001package org.kohsuke.stapler;
002
003import org.apache.commons.lang.StringUtils;
004import org.kohsuke.stapler.lang.Klass;
005
006import javax.servlet.RequestDispatcher;
007import javax.servlet.ServletException;
008import javax.servlet.ServletRequest;
009import javax.servlet.ServletResponse;
010import javax.servlet.http.HttpServletRequest;
011import javax.servlet.http.HttpServletResponse;
012import java.io.IOException;
013import java.net.URL;
014import java.util.ArrayList;
015import java.util.Arrays;
016import java.util.Collection;
017import java.util.List;
018
019/**
020 * @author Kohsuke Kawaguchi
021 */
022// @MetaInfServices - this facet needs to be manually configured
023public class StaticViewFacet extends Facet {
024    private final List<String> allowedExtensions = new ArrayList<String>();
025
026    public StaticViewFacet(String... allowedExtensions) {
027        this(Arrays.asList(allowedExtensions));
028    }
029
030    public StaticViewFacet(Collection<String> allowedExtensions) {
031        for (String extension : allowedExtensions) {
032            addExtension(extension);
033        }
034    }
035
036    public void addExtension(String ext) {
037        if (!ext.startsWith("."))   ext='.'+ext;
038        allowedExtensions.add(ext);
039    }
040
041    public void buildViewDispatchers(final MetaClass owner, List<Dispatcher> dispatchers) {
042        dispatchers.add(new Dispatcher() {
043            public boolean dispatch(RequestImpl req, ResponseImpl rsp, Object node) throws IOException, ServletException {
044                // check Jelly view
045                String next = req.tokens.peek();
046                if(next==null)  return false;
047
048                // only match the end of the URL
049                if (req.tokens.countRemainingTokens()>1)    return false;
050                // and avoid serving both "foo" and "foo/" as relative URL semantics are drastically different
051                if (req.getRequestURI().endsWith("/"))      return false;
052
053                URL res = findResource(owner.klass,next);
054                if(res==null)        return false;   // no Jelly script found
055
056                req.tokens.next();
057
058                Dispatcher.anonymizedTraceEval(req, rsp, node, "%s: Static view: %s", next);
059                if (traceable()) {
060                    // Null not expected here
061                    trace(req,rsp,"-> %s on <%s>", next, node);
062                }
063
064                rsp.serveFile(req, res);
065
066                return true;
067            }
068
069            public String toString() {
070                return "static file for url=/VIEW"+StringUtils.join(allowedExtensions,"|");
071            }
072        });
073    }
074
075    /**
076     * Determines if this resource can be served
077     */
078    protected URL findResource(Klass c, String fileName) {
079        boolean ends = false;
080        for (String ext : allowedExtensions) {
081            if (fileName.endsWith(ext)) {
082                ends = true;
083                break;
084            }
085        }
086        if (!ends)  return null;
087
088        for ( ; c!=null; c=c.getSuperClass()) {
089            URL res = c.getResource(fileName);
090            if (res!=null)  return res;
091        }
092        return null;
093    }
094
095    public RequestDispatcher createRequestDispatcher(RequestImpl request, Klass<?> type, Object it, String viewName) throws IOException {
096        final Stapler stapler = request.getStapler();
097        final URL res = findResource(type,viewName);
098        if (res==null)      return null;
099
100        return new RequestDispatcher() {
101            public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException {
102                stapler.serveStaticResource((HttpServletRequest)request, new ResponseImpl(stapler, (HttpServletResponse) response), res, 0);
103            }
104
105            public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException {
106                throw new UnsupportedOperationException();
107            }
108        };
109    }
110
111    @Override
112    public void buildIndexDispatchers(MetaClass owner, List<Dispatcher> dispatchers) {
113        URL res = findResource(owner.klass, "index.html");
114        if (res!=null) {
115            dispatchers.add(new IndexHtmlDispatcher(res));
116        }
117    }
118
119    public boolean handleIndexRequest(RequestImpl req, ResponseImpl rsp, Object node, MetaClass nodeMetaClass) throws IOException, ServletException {
120        URL res = findResource(nodeMetaClass.klass,"index.html");
121        if (res==null)  return false;
122        return new IndexHtmlDispatcher(res).dispatch(req,rsp,node);
123    }
124}