001/*
002 * Copyright (c) 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.ecm.webengine.jaxrs;
013
014import java.util.ArrayList;
015import java.util.HashMap;
016import java.util.HashSet;
017import java.util.List;
018import java.util.Map;
019import java.util.Set;
020
021import javax.ws.rs.Path;
022import javax.ws.rs.core.Application;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.ecm.platform.rendering.api.RenderingEngine;
027import org.nuxeo.ecm.webengine.jaxrs.servlet.config.ResourceExtension;
028import org.nuxeo.ecm.webengine.jaxrs.views.BundleResource;
029import org.nuxeo.ecm.webengine.jaxrs.views.TemplateViewMessageBodyWriter;
030import org.nuxeo.ecm.webengine.jaxrs.views.ViewMessageBodyWriter;
031import org.osgi.framework.Bundle;
032
033/**
034 * A composite JAX-RS application that can receive fragments from outside.
035 *
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038public class ApplicationHost extends Application {
039
040    private static final Log log = LogFactory.getLog(ApplicationHost.class);
041
042    protected final String name;
043
044    protected final Map<String, Boolean> features = new HashMap<String, Boolean>();
045
046    protected final List<ApplicationFragment> apps;
047
048    protected List<Reloadable> listeners;
049
050    protected RenderingEngine rendering;
051
052    /**
053     * Sub-Resources extensions
054     */
055    protected Map<String, ResourceExtension> extensions;
056
057    /**
058     * Root resource classes to owner bundles. This is a fall-back for FrameworkUtils.getBundle(class) since is not
059     * supported in all OSGi like frameworks
060     */
061    protected HashMap<Class<?>, Bundle> class2Bundles;
062
063    public ApplicationHost(String name) {
064        this.name = name;
065        apps = new ArrayList<ApplicationFragment>();
066        class2Bundles = new HashMap<Class<?>, Bundle>();
067        listeners = new ArrayList<Reloadable>();
068        extensions = new HashMap<String, ResourceExtension>();
069    }
070
071    public BundleResource getExtension(BundleResource target, String segment) {
072        ResourceExtension xt = getExtension(target.getClass().getName() + "#" + segment);
073        if (xt != null) {
074            BundleResource res = target.getResource(xt.getResourceClass());
075            if (res != null && res.accept(target)) {
076                res.getContext().pushBundle(xt.getBundle());
077                return res;
078            }
079        }
080        return null;
081    }
082
083    public RenderingEngine getRendering() {
084        return rendering;
085    }
086
087    public void setRendering(RenderingEngine rendering) {
088        this.rendering = rendering;
089    }
090
091    public synchronized void addExtension(ResourceExtension xt) {
092        extensions.put(xt.getId(), xt);
093        class2Bundles.put(xt.getResourceClass(), xt.getBundle());
094        if (rendering != null) {
095            rendering.flushCache();
096        }
097    }
098
099    public synchronized void removeExtension(ResourceExtension xt) {
100        extensions.remove(xt.getId());
101        class2Bundles.remove(xt.getResourceClass());
102        if (rendering != null) {
103            rendering.flushCache();
104        }
105    }
106
107    public synchronized ResourceExtension getExtension(String id) {
108        return extensions.get(id);
109    }
110
111    public synchronized ResourceExtension[] getExtensions(ResourceExtension xt) {
112        return extensions.values().toArray(new ResourceExtension[extensions.size()]);
113    }
114
115    public String getName() {
116        return name;
117    }
118
119    public Map<String, Boolean> getFeatures() {
120        return features;
121    }
122
123    public synchronized void add(ApplicationFragment app) {
124        apps.add(app);
125    }
126
127    public synchronized void remove(ApplicationFragment app) {
128        apps.remove(app);
129    }
130
131    public synchronized ApplicationFragment[] getApplications() {
132        return apps.toArray(new ApplicationFragment[apps.size()]);
133    }
134
135    public synchronized void addReloadListener(Reloadable listener) {
136        listeners.add(listener);
137    }
138
139    public synchronized void removeReloadListener(Reloadable listener) {
140        listeners.remove(listener);
141    }
142
143    public synchronized void reload() {
144        for (ApplicationFragment fragment : apps) {
145            fragment.reload();
146        }
147        // TODO this will not work with extension subresources - find a fix
148        class2Bundles = new HashMap<Class<?>, Bundle>();
149        for (Reloadable listener : listeners) {
150            listener.reload();
151        }
152        if (rendering != null) {
153            rendering.flushCache();
154        }
155    }
156
157    /**
158     * Get the bundle declaring the given root class. This method is not synchronized since it is assumed to be called
159     * after the application was created and before it was destroyed. <br>
160     * When a bundle is refreshing this method may throw exceptions but it is not usual to refresh bundles at runtime
161     * and making requests in same time.
162     *
163     * @param clazz
164     */
165    public Bundle getBundle(Class<?> clazz) {
166        return class2Bundles.get(clazz);
167    }
168
169    @Override
170    public synchronized Set<Class<?>> getClasses() {
171        HashSet<Class<?>> result = new HashSet<Class<?>>();
172        for (ApplicationFragment app : getApplications()) {
173            try {
174                for (Class<?> clazz : app.getClasses()) {
175                    if (clazz.isAnnotationPresent(Path.class)) {
176                        class2Bundles.put(clazz, app.getBundle());
177                    }
178                    result.add(clazz);
179                }
180            } catch (java.lang.LinkageError e) {
181                log.error(e);
182            }
183        }
184        return result;
185    }
186
187    @Override
188    public synchronized Set<Object> getSingletons() {
189        HashSet<Object> result = new HashSet<Object>();
190        result.add(new TemplateViewMessageBodyWriter());
191        result.add(new ViewMessageBodyWriter());
192        for (ApplicationFragment app : getApplications()) {
193            for (Object obj : app.getSingletons()) {
194                if (obj.getClass().isAnnotationPresent(Path.class)) {
195                    class2Bundles.put(obj.getClass(), app.getBundle());
196                }
197                result.add(obj);
198            }
199        }
200        return result;
201    }
202
203}