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.framework.adjunct;
025
026import org.apache.commons.jelly.XMLOutput;
027import org.kohsuke.stapler.Stapler;
028import org.kohsuke.stapler.StaplerRequest;
029import org.xml.sax.SAXException;
030
031import java.io.IOException;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Collection;
035import java.util.Collections;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Set;
039import java.util.logging.Level;
040import java.util.logging.Logger;
041
042/**
043 * This request-scope object keeps track of which {@link Adjunct}s are already included.
044 *
045 * @author Kohsuke Kawaguchi
046 */
047public class AdjunctsInPage {
048    private final AdjunctManager manager;
049    /**
050     * All adjuncts that are already included in the page.
051     */
052    private final Set<String> included = new HashSet<String>();
053
054    /**
055     * {@link Adjunct}s that haven't written to HTML yet because &lt;head>
056     * tag hasn't been written yet.
057     */
058    private final List<Adjunct> pending = new ArrayList<Adjunct>();
059    
060    private final StaplerRequest request;
061
062    /**
063     * Obtains the instance associated with the current request of the given {@link StaplerRequest}.
064     */
065    public static AdjunctsInPage get() {
066        return get(Stapler.getCurrentRequest());
067    }
068    /**
069     * Obtains the instance associated with the current request of the given {@link StaplerRequest}.
070     *
071     * <p>
072     * This method is handy when the caller already have the request object around,
073     * so that we can save {@link Stapler#getCurrentRequest()} call.
074     */
075    public static AdjunctsInPage get(StaplerRequest request) {
076        AdjunctsInPage aip = (AdjunctsInPage) request.getAttribute(KEY);
077        if(aip==null)
078            request.setAttribute(KEY,aip=new AdjunctsInPage(AdjunctManager.get(request.getServletContext()),request));
079        return aip;
080    }
081
082    private AdjunctsInPage(AdjunctManager manager,StaplerRequest request) {
083        this.manager = manager;
084        this.request = request;
085    }
086
087    /**
088     * Gets what has been already included/assumed.
089     *
090     * This method returns a live unmodifiable view of what's included.
091     * So if at some later point more adjuncts are loaded, the view
092     * obtained earlier will reflect that.
093     */
094    public Set<String> getIncluded() {
095        return Collections.unmodifiableSet(included);
096    }
097
098    /**
099     * Checks if something has already been included/assumed.
100     */
101    public boolean isIncluded(String include) {
102        return included.contains(include);
103    }
104
105    /**
106     * Generates the script tag and CSS link tag to include necessary adjuncts,
107     * and records the fact that those adjuncts are already included in the page,
108     * so that it won't be loaded again.
109     */
110    public void generate(XMLOutput out, String... includes) throws IOException, SAXException {
111        List<Adjunct> needed = new ArrayList<Adjunct>();
112        for (String include : includes)
113            findNeeded(include,needed);
114
115        for (Adjunct adj : needed)
116            adj.write(request,out);
117    }
118
119    /**
120     * When you include your version of the adjunct externally, you can use
121     * this method to inform {@link AdjunctsInPage} that those adjuncts are
122     * already included in the page.
123     */
124    public void assumeIncluded(String... includes) throws IOException, SAXException {
125        assumeIncluded(Arrays.asList(includes));
126    }
127
128    public void assumeIncluded(Collection<String> includes) throws IOException, SAXException {
129        List<Adjunct> needed = new ArrayList<Adjunct>();
130        for (String include : includes)
131            findNeeded(include,needed);
132    }
133
134    /**
135     * Works like the {@link #generate(XMLOutput, String...)} method
136     * but just put the adjuncts to {@link #pending} without writing it.
137     */
138    public void spool(String... includes) throws IOException, SAXException {
139        for (String include : includes)
140            findNeeded(include,pending);
141    }
142
143    /**
144     * Writes out what's spooled by {@link #spool(String...)} method.
145     */
146    public void writeSpooled(XMLOutput out) throws SAXException, IOException {
147        for (Adjunct adj : pending)
148            adj.write(request,out);
149        pending.clear();
150    }
151
152    /**
153     * Builds up the needed adjuncts into the 'needed' list.
154     */
155    private void findNeeded(String include, List<Adjunct> needed) throws IOException {
156        if(!included.add(include))
157            return; // already sent
158
159        // list dependencies first
160        try {
161            Adjunct a = manager.get(include);
162            for (String req : a.required)
163                findNeeded(req,needed);
164            needed.add(a);
165        } catch (NoSuchAdjunctException e) {
166            LOGGER.log(Level.WARNING, "No such adjunct found: "+include,e);
167        }
168    }
169
170    private static final String KEY = AdjunctsInPage.class.getName();
171
172    private static final Logger LOGGER = Logger.getLogger(AdjunctsInPage.class.getName());
173}