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 org.apache.commons.jelly.JellyContext;
027import org.apache.commons.jelly.JellyTagException;
028import org.apache.commons.jelly.Script;
029import org.apache.commons.jelly.TagSupport;
030import org.apache.commons.jelly.XMLOutput;
031import org.apache.commons.jelly.impl.TagScript;
032import org.kohsuke.stapler.MetaClass;
033import org.kohsuke.stapler.WebApp;
034import org.xml.sax.SAXException;
035import org.jvnet.maven.jellydoc.annotation.Required;
036
037/**
038 * Tag that includes views of the object.
039 *
040 * @author Kohsuke Kawaguchi
041 */
042public class IncludeTag extends TagSupport {
043    private Object it;
044
045    private String page;
046
047    private Object from;
048
049    private boolean optional;
050
051    private Class clazz;
052
053    /**
054     * Specifies the name of the JSP to be included.
055     */
056    @Required
057    public void setPage(String page) {
058        this.page = page;
059    }
060
061    /**
062     * Specifies the object for which JSP will be included.
063     * Defaults to the "it" object in the current context.
064     */
065    public void setIt(Object it) {
066        this.it = it;
067    }
068
069    /**
070     * When loading the script, use the classloader from this object
071     * to locate the script. Otherwise defaults to "it" object.
072     */
073    public void setFrom(Object from) {
074        this.from = from;
075    }
076
077    /**
078     * When loading script, load from this class.
079     *
080     * By default this is "from.getClass()". This takes
081     * precedence over the {@link #setFrom(Object)} method.
082     */
083    public void setClass(Class clazz) {
084        this.clazz = clazz;
085    }
086
087    /**
088     * If true, not finding the page is not an error.
089     * (And in such a case, the body of the include tag is evaluated instead.)
090     */
091    public void setOptional(boolean optional) {
092        this.optional = optional;
093    }
094
095    public void doTag(XMLOutput output) throws JellyTagException {
096        if(page==null) {
097            // this makes it convenient when the caller wants to gracefully the expression for @page
098            // otherwise this results in http://pastie.org/601828
099            if (optional) {
100                invokeBody(output);
101                return;
102            }
103            throw new JellyTagException("The page attribute is not specified");
104        }
105        Object it = this.it;
106        if(it==null)
107            it = getContext().getVariable("it");
108
109        MetaClass c = WebApp.getCurrent().getMetaClass(getScriptClass(it));
110        Script script;
111        try {
112            script = c.loadTearOff(JellyClassTearOff.class).findScript(page);
113        } catch (RuntimeException e) {
114            throw e;
115        } catch (Exception e) {
116            throw new JellyTagException("Error loading '"+page+"' for "+c.klass,e);
117        }
118
119        if(script==null) {
120            if(optional) {
121                invokeBody(output);
122                return;
123            }
124            throw new JellyTagException("No page found '"+page+"' for "+c.klass);
125        }
126
127        context = new JellyContext(getContext());
128        context.setExportLibraries(false);
129        if(this.it!=null)
130            context.setVariable("it",this.it);
131        context.setVariable("from", from!=null?from:it);
132
133        ClassLoader old = Thread.currentThread().getContextClassLoader();
134        Thread.currentThread().setContextClassLoader(c.classLoader.loader);
135        try {
136            String source = null;
137            if(JellyFacet.TRACE) {
138                if (script instanceof TagScript) {
139                    TagScript ts = (TagScript) script;
140                    source = ts.getFileName();
141                } else
142                    source = page+" (exact source location unknown)";
143
144                String msg = "\nBegin " + source+'\n';
145                output.comment(msg.toCharArray(),0,msg.length());
146            }
147            script.run(context,output);
148            if(source!=null) {
149                String msg = "\nEnd " + source+'\n';
150                output.comment(msg.toCharArray(),0,msg.length());
151            }
152        } catch (SAXException e) {
153            throw new JellyTagException(e);
154        } finally {
155            Thread.currentThread().setContextClassLoader(old);
156        }
157    }
158
159    private Class getScriptClass(Object it) {
160        if (clazz != null) return clazz;
161        if (from != null) return from.getClass();
162        else return it.getClass();
163    }
164}