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