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<>();
097        this.filters = new ArrayList<>();
098        this.resources = new HashMap<>();
099        this.contexts = new HashMap<>();
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<>();
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    public synchronized void initHttpService(HttpService service) throws ServletException, NamespaceException {
133        if (this.service == null) {
134            this.service = service;
135            installServlets();
136        }
137    }
138
139    public HttpService getHttpService() {
140        return service;
141    }
142
143    public synchronized void addServlet(ServletDescriptor descriptor) throws ServletException, NamespaceException {
144        servlets.add(descriptor);
145        installServlet(descriptor);
146    }
147
148    public synchronized void removeServlet(ServletDescriptor descriptor) {
149        servlets.remove(descriptor);
150        contexts.remove(descriptor.path);
151        if (service != null) {
152            // destroy first the listeners if any was initialized
153            ListenerSetDescriptor lsd = descriptor.getListenerSet();
154            if (lsd != null) {
155                lsd.destroy();
156            }
157            // unregister the servlet
158            service.unregister(descriptor.path);
159        }
160    }
161
162    public synchronized void reloadServlet(ServletDescriptor descriptor) throws ServletException, NamespaceException {
163        removeServlet(descriptor);
164        addServlet(descriptor);
165    }
166
167    public synchronized void addFilterSet(FilterSetDescriptor filter) {
168        filters.add(filter);
169    }
170
171    public synchronized void removeFilterSet(FilterSetDescriptor filter) {
172        filters.remove(filter);
173    }
174
175    public synchronized void addResources(ResourcesDescriptor rd) {
176        List<ResourcesDescriptor> list = resources.get(rd.getServlet());
177        if (list == null) {
178            list = new ArrayList<>();
179        }
180        list.add(rd);
181        // update context
182        BundleHttpContext ctx = contexts.get(rd.getServlet());
183        if (ctx != null) {
184            ctx.setResources(list.toArray(new ResourcesDescriptor[list.size()]));
185        }
186    }
187
188    public synchronized void removeResources(ResourcesDescriptor rd) {
189        List<ResourcesDescriptor> list = resources.get(rd.getServlet());
190        if (list != null) {
191            if (list.remove(rd)) {
192                if (list.isEmpty()) {
193                    resources.remove(rd.getServlet());
194                }
195                // update context
196                BundleHttpContext ctx = contexts.get(rd.getServlet());
197                if (ctx != null) {
198                    ctx.setResources(list.toArray(new ResourcesDescriptor[list.size()]));
199                }
200            }
201        }
202    }
203
204    private synchronized void installServlets() throws ServletException, NamespaceException {
205        if (service != null) {
206            for (ServletDescriptor sd : servlets) {
207                installServlet(sd);
208            }
209        }
210    }
211
212    private void installServlet(ServletDescriptor sd) throws ServletException, NamespaceException {
213        if (service != null) {
214            // ClassRef ref = sd.getClassRef();
215            BundleHttpContext ctx = new BundleHttpContext(sd.bundle, sd.resources);
216            List<ResourcesDescriptor> rd = resources.get(sd.path);
217            // register resources contributed so far
218            if (rd != null) {
219                ctx.setResources(rd.toArray(new ResourcesDescriptor[rd.size()]));
220            }
221            Hashtable<String, String> params = new Hashtable<>();
222            if (sd.name != null) {
223                params.putAll(sd.getInitParams());
224                params.put(SERVLET_NAME, sd.name);
225            }
226            service.registerServlet(sd.path, new ServletHolder(), params, ctx);
227            contexts.put(sd.path, ctx);
228        }
229    }
230
231    // static class BundleHttpContext implements HttpContext {
232    // protected Bundle bundle;
233    // public BundleHttpContext(Bundle bundle) {
234    // this.bundle = bundle;
235    // }
236    // @Override
237    // public String getMimeType(String name) {
238    // return null;
239    // }
240    // @Override
241    // public URL getResource(String name) {
242    // return null;
243    // }
244    // @Override
245    // public boolean handleSecurity(HttpServletRequest request,
246    // HttpServletResponse response) throws IOException {
247    // // default behaviour assumes the container has already performed authentication
248    // return true;
249    // }
250    // }
251}