001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.ecm.webengine.jaxrs.servlet.config;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.Hashtable;
024import java.util.List;
025import java.util.Map;
026
027import javax.servlet.ServletException;
028
029import org.nuxeo.ecm.webengine.jaxrs.Activator;
030import org.nuxeo.ecm.webengine.jaxrs.servlet.ServletHolder;
031import org.osgi.framework.Bundle;
032import org.osgi.service.http.HttpService;
033import org.osgi.service.http.NamespaceException;
034
035/**
036 * Handle servlet registration from Nuxeo extension points. This class is a singleton shared by the {@link Activator}
037 * and the {@link ServletRegistryComponent} component. Because we don't have yet a solution to synchronize the
038 * initialization time of the Activator and a Nuxeo component we are using a singleton instance to be able
039 *
040 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
041 */
042public class ServletRegistry {
043
044    public static final String SERVLET_NAME = ServletRegistry.class.getName() + ".name";
045
046    private static volatile ServletRegistry instance;
047
048    public static ServletRegistry getInstance() {
049        ServletRegistry reg = instance;
050        if (reg == null) {
051            synchronized (ServletRegistry.class) {
052                reg = new ServletRegistry();
053                instance = reg;
054            }
055        }
056        return reg;
057    }
058
059    public static synchronized void dispose() {
060        instance = null;
061    }
062
063    /**
064     * Servlets contributed to the extension points. Servlets are contributed to the {@link HttpService} when it becomes
065     * available.
066     */
067    protected List<ServletDescriptor> servlets;
068
069    protected List<FilterSetDescriptor> filters;
070
071    /**
072     * Store resources contributed from external bundles to servlets. Map the servlet path to the list of contributed
073     * resources
074     */
075    protected Map<String, List<ResourcesDescriptor>> resources;
076
077    /**
078     * The registered HttpContext mapped to the servlet path. An HttpContext is created and inserted in this map when
079     * its servlet is registered against the HttpService. The context is removed when the servlet is unregsitered.
080     * <p>
081     * Resource contributions are injected into the context and reinjected each time the context is restarted.
082     */
083    protected Map<String, BundleHttpContext> contexts;
084
085    /**
086     * The HttpService instance is injected when the service becomes available by the Activator.
087     */
088    protected HttpService service;
089
090    /**
091     * The bundle owning this class
092     */
093    protected Bundle bundle;
094
095    private ServletRegistry() {
096        this.servlets = new ArrayList<ServletDescriptor>();
097        this.filters = new ArrayList<FilterSetDescriptor>();
098        this.resources = new HashMap<String, List<ResourcesDescriptor>>();
099        this.contexts = new HashMap<String, BundleHttpContext>();
100    }
101
102    public synchronized ServletDescriptor[] getServletDescriptors() {
103        return servlets.toArray(new ServletDescriptor[servlets.size()]);
104    }
105
106    public synchronized FilterSetDescriptor[] getFilterSetDescriptors() {
107        return filters.toArray(new FilterSetDescriptor[filters.size()]);
108    }
109
110    public ServletDescriptor getServletDescriptor(String name) {
111        for (ServletDescriptor servlet : getServletDescriptors()) {
112            if (name.equals(servlet.name)) {
113                return servlet;
114            }
115        }
116        return null;
117    }
118
119    public List<FilterSetDescriptor> getFiltersFor(String name) {
120        ArrayList<FilterSetDescriptor> list = new ArrayList<FilterSetDescriptor>();
121        for (FilterSetDescriptor filter : getFilterSetDescriptors()) {
122            if (name.equals(filter.targetServlet)) {
123                list.add(filter);
124            }
125        }
126        return list;
127    }
128
129    /**
130     * Called by the service tracker when HttpService is up to configure it with current contributed servlets
131     *
132     * @param service
133     */
134    public synchronized void initHttpService(HttpService service) throws ServletException, NamespaceException {
135        if (this.service == null) {
136            this.service = service;
137            installServlets();
138        }
139    }
140
141    public HttpService getHttpService() {
142        return service;
143    }
144
145    public synchronized void addServlet(ServletDescriptor descriptor) throws ServletException, NamespaceException {
146        servlets.add(descriptor);
147        installServlet(descriptor);
148    }
149
150    public synchronized void removeServlet(ServletDescriptor descriptor) {
151        servlets.remove(descriptor);
152        contexts.remove(descriptor.path);
153        if (service != null) {
154            // destroy first the listeners if any was initialized
155            ListenerSetDescriptor lsd = descriptor.getListenerSet();
156            if (lsd != null) {
157                lsd.destroy();
158            }
159            // unregister the servlet
160            service.unregister(descriptor.path);
161        }
162    }
163
164    public synchronized void reloadServlet(ServletDescriptor descriptor) throws ServletException, NamespaceException {
165        removeServlet(descriptor);
166        addServlet(descriptor);
167    }
168
169    public synchronized void addFilterSet(FilterSetDescriptor filter) {
170        filters.add(filter);
171    }
172
173    public synchronized void removeFilterSet(FilterSetDescriptor filter) {
174        filters.remove(filter);
175    }
176
177    public synchronized void addResources(ResourcesDescriptor rd) {
178        List<ResourcesDescriptor> list = resources.get(rd.getServlet());
179        if (list == null) {
180            list = new ArrayList<ResourcesDescriptor>();
181        }
182        list.add(rd);
183        // update context
184        BundleHttpContext ctx = contexts.get(rd.getServlet());
185        if (ctx != null) {
186            ctx.setResources(list.toArray(new ResourcesDescriptor[list.size()]));
187        }
188    }
189
190    public synchronized void removeResources(ResourcesDescriptor rd) {
191        List<ResourcesDescriptor> list = resources.get(rd.getServlet());
192        if (list != null) {
193            if (list.remove(rd)) {
194                if (list.isEmpty()) {
195                    resources.remove(rd.getServlet());
196                }
197                // update context
198                BundleHttpContext ctx = contexts.get(rd.getServlet());
199                if (ctx != null) {
200                    ctx.setResources(list.toArray(new ResourcesDescriptor[list.size()]));
201                }
202            }
203        }
204    }
205
206    private synchronized void installServlets() throws ServletException, NamespaceException {
207        if (service != null) {
208            for (ServletDescriptor sd : servlets) {
209                installServlet(sd);
210            }
211        }
212    }
213
214    private void installServlet(ServletDescriptor sd) throws ServletException, NamespaceException {
215        if (service != null) {
216            // ClassRef ref = sd.getClassRef();
217            BundleHttpContext ctx = new BundleHttpContext(sd.bundle, sd.resources);
218            List<ResourcesDescriptor> rd = resources.get(sd.path);
219            // register resources contributed so far
220            if (rd != null) {
221                ctx.setResources(rd.toArray(new ResourcesDescriptor[rd.size()]));
222            }
223            Hashtable<String, String> params = new Hashtable<String, String>();
224            if (sd.name != null) {
225                params.putAll(sd.getInitParams());
226                params.put(SERVLET_NAME, sd.name);
227            }
228            service.registerServlet(sd.path, new ServletHolder(), params, ctx);
229            contexts.put(sd.path, ctx);
230        }
231    }
232
233    // static class BundleHttpContext implements HttpContext {
234    // protected Bundle bundle;
235    // public BundleHttpContext(Bundle bundle) {
236    // this.bundle = bundle;
237    // }
238    // @Override
239    // public String getMimeType(String name) {
240    // return null;
241    // }
242    // @Override
243    // public URL getResource(String name) {
244    // return null;
245    // }
246    // @Override
247    // public boolean handleSecurity(HttpServletRequest request,
248    // HttpServletResponse response) throws IOException {
249    // // default behaviour assumes the container has already performed authentication
250    // return true;
251    // }
252    // }
253}