001/*
002 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 */
017package org.nuxeo.ecm.webengine.app;
018
019import java.io.BufferedReader;
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.InputStreamReader;
024import java.net.URL;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.HashSet;
028import java.util.Map;
029import java.util.Set;
030
031import javax.ws.rs.Path;
032import javax.ws.rs.core.Application;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.ecm.webengine.WebEngine;
037import org.nuxeo.ecm.webengine.jaxrs.ApplicationFactory;
038import org.nuxeo.ecm.webengine.jaxrs.scan.Scanner;
039import org.nuxeo.ecm.webengine.loader.WebLoader;
040import org.nuxeo.ecm.webengine.model.Module;
041import org.nuxeo.ecm.webengine.model.WebObject;
042import org.nuxeo.ecm.webengine.model.impl.DefaultTypeLoader;
043import org.nuxeo.ecm.webengine.model.impl.ModuleConfiguration;
044import org.nuxeo.ecm.webengine.model.impl.ModuleManager;
045import org.osgi.framework.Bundle;
046
047/**
048 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
049 */
050public class WebEngineModule extends Application implements ApplicationFactory {
051
052    private final static Log log = LogFactory.getLog(WebEngineModule.class);
053
054    public final static String WEBOBJECT_ANNO = "Lorg/nuxeo/ecm/webengine/model/WebObject;";
055
056    public final static String WEBADAPTER_ANNO = "Lorg/nuxeo/ecm/webengine/model/WebAdapter;";
057
058    protected Bundle bundle;
059
060    protected ModuleConfiguration cfg;
061
062    void init(WebEngine engine, Bundle bundle, File configuration, Map<String, String> attrs)
063            throws ReflectiveOperationException, IOException {
064        this.bundle = bundle;
065        loadModuleConfigurationFile(engine, configuration);
066        if (attrs != null) {
067            String v = attrs.get("name");
068            if (v != null) {
069                cfg.name = v;
070            }
071            v = attrs.get("extends");
072            if (v != null) {
073                cfg.base = v;
074            }
075            v = attrs.get("headless");
076            if (v != null) {
077                cfg.isHeadless = Boolean.parseBoolean(v);
078            }
079        }
080        if (cfg.name == null) {
081            throw new IllegalStateException("No name given for web module in bundle " + bundle.getSymbolicName());
082        }
083        initTypes(bundle, attrs.get("package"), engine);
084    }
085
086    private void initTypes(Bundle bundle, String packageBase, WebEngine engine) throws ReflectiveOperationException,
087            IOException {
088        cfg.types = getWebTypes();
089        if (cfg.types == null) {
090            // try the META-INF/web-types file
091            loadMetaTypeFile(engine);
092            if (cfg.types == null) {
093                // try scanning the bundle
094                scan(bundle, packageBase);
095                if (cfg.types == null) {
096                    throw new IllegalStateException("No web types defined in web module " + cfg.name + " from bundle "
097                            + bundle.getSymbolicName());
098                }
099            } else {
100                initRoots(engine);
101            }
102        } else {
103            initRoots(engine);
104        }
105    }
106
107    private void scan(Bundle bundle, String packageBase) throws ReflectiveOperationException, IOException {
108        if (packageBase == null) {
109            packageBase = "/";
110        }
111        Scanner scanner = new Scanner(bundle, packageBase, Scanner.PATH_ANNO, Scanner.PROVIDER_ANNO, WEBOBJECT_ANNO,
112                WEBADAPTER_ANNO);
113        scanner.scan();
114        Collection<Class<?>> paths = scanner.getCollector(Scanner.PATH_ANNO);
115        Collection<Class<?>> providers = scanner.getCollector(Scanner.PROVIDER_ANNO);
116        cfg.roots = new Class<?>[paths.size() + providers.size()];
117        int i = 0;
118        for (Class<?> cl : paths) {
119            cfg.roots[i++] = cl;
120        }
121        for (Class<?> cl : providers) {
122            cfg.roots[i++] = cl;
123        }
124        Collection<Class<?>> objs = scanner.getCollector(WEBOBJECT_ANNO);
125        Collection<Class<?>> adapters = scanner.getCollector(WEBADAPTER_ANNO);
126        cfg.types = new Class<?>[objs.size() + adapters.size()];
127        i = 0;
128        for (Class<?> cl : objs) {
129            cfg.types[i++] = cl;
130        }
131        for (Class<?> cl : adapters) {
132            cfg.types[i++] = cl;
133        }
134    }
135
136    private void loadMetaTypeFile(WebEngine engine) throws ReflectiveOperationException, IOException {
137        URL url = bundle.getEntry(DefaultTypeLoader.WEB_TYPES_FILE);
138        if (url != null) {
139            InputStream in = url.openStream();
140            try {
141                cfg.types = readWebTypes(engine.getWebLoader(), in);
142            } finally {
143                in.close();
144            }
145        }
146    }
147
148    private static Class<?>[] readWebTypes(WebLoader loader, InputStream in) throws ReflectiveOperationException,
149            IOException {
150        HashSet<Class<?>> types = new HashSet<Class<?>>();
151        BufferedReader reader = null;
152        try {
153            reader = new BufferedReader(new InputStreamReader(in));
154            String line;
155            while ((line = reader.readLine()) != null) {
156                line = line.trim();
157                if (line.length() == 0 || line.startsWith("#")) {
158                    continue;
159                }
160                int p = line.indexOf('|');
161                if (p > -1) {
162                    line = line.substring(0, p);
163                }
164                Class<?> cl = loader.loadClass(line);
165                types.add(cl);
166            }
167        } finally {
168            if (reader != null) {
169                try {
170                    reader.close();
171                } catch (IOException e) {
172                }
173            }
174        }
175        return types.toArray(new Class<?>[types.size()]);
176    }
177
178    private void initRoots(WebEngine engine) {
179        ArrayList<Class<?>> roots = new ArrayList<Class<?>>();
180        for (Class<?> cl : cfg.types) {
181            if (cl.isAnnotationPresent(Path.class)) {
182                roots.add(cl);
183            } else if (cfg.rootType != null) {
184                // compat mode - should be removed later
185                WebObject wo = cl.getAnnotation(WebObject.class);
186                if (wo != null && wo.type().equals(cfg.rootType)) {
187                    log.warn("Invalid web module " + cfg.name + " from bundle " + bundle.getSymbolicName()
188                            + ". The root-type " + cl
189                            + " in module.xml is deprecated. Consider using @Path annotation on you root web objects.");
190                }
191            }
192        }
193        if (roots.isEmpty()) {
194            log.error("No root web objects found in web module " + cfg.name + " from bundle "
195                    + bundle.getSymbolicName());
196            // throw new
197            // IllegalStateException("No root web objects found in web module "+cfg.name+" from bundle "+bundle.getSymbolicName());
198        }
199        cfg.roots = roots.toArray(new Class<?>[roots.size()]);
200    }
201
202    private ModuleConfiguration loadModuleConfigurationFile(WebEngine engine, File file) throws IOException {
203        if (file != null && file.isFile()) {
204            cfg = ModuleManager.readConfiguration(engine, file);
205        } else {
206            cfg = new ModuleConfiguration();
207        }
208        cfg.engine = engine;
209        cfg.file = file;
210        return cfg;
211    }
212
213    public ModuleConfiguration getConfiguration() {
214        return cfg;
215    }
216
217    public Module getModule() {
218        return cfg.get();
219    }
220
221    public Bundle getBundle() {
222        return bundle;
223    }
224
225    @Override
226    public Set<Class<?>> getClasses() {
227        if (cfg.roots == null) {
228            return new HashSet<Class<?>>();
229        }
230        HashSet<Class<?>> set = new HashSet<Class<?>>();
231        for (Class<?> root : cfg.roots) {
232            set.add(root);
233        }
234        return set;
235    }
236
237    public String getId() {
238        return bundle.getSymbolicName();
239    }
240
241    public Class<?>[] getWebTypes() {
242        return null; // types will be discovered
243    }
244
245    @Override
246    public Application getApplication(Bundle bundle, Map<String, String> args) throws ReflectiveOperationException,
247            IOException {
248        return WebEngineModuleFactory.getApplication(this, bundle, args);
249    }
250
251}