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