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