001/*
002 * Copyright (c) 2004-2010, Kohsuke Kawaguchi
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
006 * that the following conditions are met:
007 *
008 *     * Redistributions of source code must retain the above copyright notice, this list of
009 *       conditions and the following disclaimer.
010 *     * Redistributions in binary form must reproduce the above copyright notice, this list of
011 *       conditions and the following disclaimer in the documentation and/or other materials
012 *       provided with the distribution.
013 *
014 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
015 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
016 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
017 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
018 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
019 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
020 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
021 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022 */
023
024package org.kohsuke.stapler.jelly;
025
026import com.google.common.cache.CacheBuilder;
027import com.google.common.cache.CacheLoader;
028import com.google.common.cache.LoadingCache;
029import org.apache.commons.jelly.JellyContext;
030import org.apache.commons.jelly.JellyException;
031import org.apache.commons.jelly.TagLibrary;
032import org.apache.commons.jelly.expression.ExpressionFactory;
033import org.apache.commons.jelly.expression.jexl.JexlExpressionFactory;
034import org.kohsuke.stapler.MetaClassLoader;
035
036import java.lang.ref.WeakReference;
037import java.net.URL;
038
039/**
040 * {@link MetaClassLoader} tear-off for Jelly support.
041 *
042 * @author Kohsuke Kawaguchi
043 */
044public class JellyClassLoaderTearOff {
045    private final MetaClassLoader owner;
046
047    /**
048     * See {@link JellyClassTearOff#scripts} for why we use {@link WeakReference} here.
049     */
050    private volatile WeakReference<LoadingCache<String,TagLibrary>> taglibs;
051
052    public static ExpressionFactory EXPRESSION_FACTORY = new JexlExpressionFactory();
053
054    public JellyClassLoaderTearOff(MetaClassLoader owner) {
055        this.owner = owner;
056    }
057
058    public TagLibrary getTagLibrary(String nsUri) {
059        LoadingCache<String,TagLibrary> m=null;
060        if(taglibs!=null)
061            m = taglibs.get();
062        if(m==null) {
063            m = CacheBuilder.newBuilder().build(new CacheLoader<String,TagLibrary>() {
064                public TagLibrary load(String nsUri) {
065                    if(owner.parent!=null) {
066                        // parent first
067                        TagLibrary tl = owner.parent.loadTearOff(JellyClassLoaderTearOff.class).getTagLibrary(nsUri);
068                        if(tl!=null)    return tl;
069                    }
070
071                    String taglibBasePath = trimHeadSlash(nsUri);
072                    try {
073                        URL res = owner.loader.getResource(taglibBasePath +"/taglib");
074                        if(res!=null)
075                        return new CustomTagLibrary(createContext(),owner.loader,nsUri,taglibBasePath);
076                    } catch (IllegalArgumentException e) {
077                        // if taglibBasePath doesn't even look like an URL, getResource throws IllegalArgumentException.
078                        // see http://old.nabble.com/bug-1.331-to26145963.html
079                    }
080
081                    // support URIs like "this:it" or "this:instance". Note that "this" URI itself is registered elsewhere
082                    if (nsUri.startsWith("this:"))
083                        try {
084                            return new ThisTagLibrary(EXPRESSION_FACTORY.createExpression(nsUri.substring(5)));
085                        } catch (JellyException e) {
086                            throw new IllegalArgumentException("Illegal expression in the URI: "+nsUri,e);
087                        }
088
089                    if (nsUri.equals("jelly:stapler"))
090                        return new StaplerTagLibrary();
091
092                    return NO_SUCH_TAGLIBRARY;    // "not found" is also cached.
093                }
094            });
095            taglibs = new WeakReference<LoadingCache<String,TagLibrary>>(m);
096        }
097
098        TagLibrary tl = m.getUnchecked(nsUri);
099        if (tl==NO_SUCH_TAGLIBRARY)     return null;
100        return tl;
101    }
102
103    private String trimHeadSlash(String nsUri) {
104        if(nsUri.startsWith("/"))
105            return nsUri.substring(1);
106        else
107            return nsUri;
108    }
109
110    /**
111     * Creates {@link JellyContext} for compiling view scripts
112     * for classes in this classloader.
113     */
114    public JellyContext createContext() {
115        JellyContext context = new CustomJellyContext(ROOT_CONTEXT);
116        context.setClassLoader(owner.loader);
117        context.setExportLibraries(false);
118        return context;
119    }
120
121    /**
122     * Used as the root context for compiling scripts.
123     */
124    private static final JellyContext ROOT_CONTEXT = new CustomJellyContext();
125
126    /**
127     * Place holder in the cache to indicate "no such taglib"
128     */
129    private static final TagLibrary NO_SUCH_TAGLIBRARY = new TagLibrary() {};
130
131}