001/*
002 * (C) Copyright 2006-2008 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.model.impl;
020
021import java.io.BufferedInputStream;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.net.URL;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.concurrent.ConcurrentHashMap;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.common.xmap.Context;
034import org.nuxeo.common.xmap.XMap;
035import org.nuxeo.ecm.webengine.ResourceBinding;
036import org.nuxeo.ecm.webengine.WebEngine;
037import org.nuxeo.ecm.webengine.WebException;
038import org.nuxeo.runtime.api.Framework;
039
040/**
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public class ModuleManager {
044
045    private static final Log log = LogFactory.getLog(ModuleManager.class);
046
047    protected final Map<String, ModuleConfiguration> modules;
048
049    protected final Map<String, ModuleConfiguration> paths;
050
051    protected final Map<String, ModuleConfiguration> roots;
052
053    protected WebEngine engine;
054
055    public ModuleManager(WebEngine engine) {
056        this.engine = engine;
057        modules = new ConcurrentHashMap<String, ModuleConfiguration>();
058        paths = new ConcurrentHashMap<String, ModuleConfiguration>();
059        roots = new ConcurrentHashMap<String, ModuleConfiguration>();
060    }
061
062    /**
063     * Gets a module given its name.
064     *
065     * @return the module or null if none
066     */
067    public ModuleConfiguration getModule(String key) {
068        return modules.get(key);
069    }
070
071    public ModuleConfiguration getModuleByPath(String path) {
072        if (!path.startsWith("/")) {
073            path = "/" + path;
074        }
075        return paths.get(path);
076    }
077
078    public ModuleConfiguration getRootModule() {
079        return paths.get("/");
080    }
081
082    public ModuleConfiguration[] getModules() {
083        return modules.values().toArray(new ModuleConfiguration[modules.size()]);
084    }
085
086    public ModuleConfiguration getModuleByConfigFile(File file) {
087        ModuleConfiguration[] ar = getModules();
088        for (ModuleConfiguration mc : ar) {
089            if (file.equals(mc.file)) {
090                return mc;
091            }
092        }
093        return null;
094    }
095
096    public synchronized void registerModule(ModuleConfiguration descriptor) {
097        log.info("Registering web module: " + descriptor.name);
098        modules.put(descriptor.name, descriptor);
099        String path = descriptor.path;
100        if (path != null) {
101            // TODO remove this
102            // compat. method now modules should be declared through
103            // WebApplication class
104            if (!path.startsWith("/")) {
105                path = "/" + path;
106            }
107            paths.put(path, descriptor);
108        }
109        if (descriptor.roots != null) {
110            for (Class<?> cl : descriptor.roots) {
111                roots.put(cl.getName(), descriptor);
112            }
113        }
114    }
115
116    // TODO the class path is not updated by this operation ...
117    public synchronized File unregisterModule(String name) {
118        ModuleConfiguration md = modules.remove(name);
119        if (md == null) {
120            return null;
121        }
122        Iterator<ModuleConfiguration> it = paths.values().iterator();
123        while (it.hasNext()) { // remove all module occurrence in paths map
124            ModuleConfiguration p = it.next();
125            if (p.name.equals(md.name)) {
126                it.remove();
127            }
128        }
129        if (md.roots != null) {
130            for (Class<?> cl : md.roots) {
131                roots.remove(cl);
132            }
133        }
134        return md.file;
135    }
136
137    public ModuleConfiguration getModuleByRootClass(Class<?> clazz) {
138        return roots.get(clazz.getName());
139    }
140
141    public synchronized void bind(String name, String path) {
142        ModuleConfiguration md = modules.get(name);
143        if (md != null) {
144            paths.put(path, md);
145        }
146    }
147
148    public void loadModules(File root) {
149        for (String name : root.list()) {
150            String path = name + "/module.xml";
151            File file = new File(root, path);
152            if (file.isFile()) {
153                loadModule(file);
154            }
155        }
156    }
157
158    public void loadModule(ModuleConfiguration mc) {
159        // this should be called after the class path is updated ...
160        loadModuleRootResources(mc);
161        mc.setEngine(engine);
162        registerModule(mc);
163    }
164
165    public void loadModule(File file) {
166        ModuleConfiguration md = loadConfiguration(file);
167        // this should be called after the class path is updated ...
168        loadModuleRootResources(md);
169        md.setEngine(engine);
170        registerModule(md);
171    }
172
173    public void loadModuleFromDir(File moduleRoot) {
174        File file = new File(moduleRoot, "module.xml");
175        if (file.isFile()) {
176            loadModule(file);
177        }
178    }
179
180    public void reloadModule(String name) {
181        log.info("Reloading module: " + name);
182        File cfg = unregisterModule(name);
183        if (cfg != null) {
184            loadModule(cfg);
185        }
186    }
187
188    public void reloadModules() {
189        log.info("Reloading modules");
190        for (ModuleConfiguration mc : getModules()) {
191            reloadModule(mc.name);
192        }
193    }
194
195    protected ModuleConfiguration loadConfiguration(File file) {
196        if (engine == null) {
197            engine = Framework.getLocalService(WebEngine.class);
198        }
199        try {
200            ModuleConfiguration mc = readConfiguration(engine, file);
201            mc.file = file;
202            if (mc.directory == null) {
203                mc.directory = file.getParentFile().getCanonicalFile();
204            }
205            return mc;
206        } catch (IOException e) {
207            throw WebException.wrap("Faile to load module configuration: " + file, e);
208        }
209    }
210
211    public static ModuleConfiguration readConfiguration(final WebEngine engine, File file) throws IOException {
212        XMap xmap = new XMap();
213        xmap.register(ModuleConfiguration.class);
214        InputStream in = new BufferedInputStream(new FileInputStream(file));
215        ModuleConfiguration mc = (ModuleConfiguration) xmap.load(createXMapContext(engine), in);
216        return mc;
217    }
218
219    public void loadModuleRootResources(ModuleConfiguration mc) {
220        if (mc.resources != null) {
221            for (ResourceBinding rb : mc.resources) {
222                try {
223                    rb.resolve(engine);
224                    engine.addResourceBinding(rb);
225                } catch (ClassNotFoundException e) {
226                    throw WebException.wrap("Faile to load module root resource: " + rb, e);
227                }
228            }
229        }
230    }
231
232    protected static Context createXMapContext(final WebEngine engine) {
233        return new Context() {
234            private static final long serialVersionUID = 1L;
235
236            @Override
237            public Class<?> loadClass(String className) throws ClassNotFoundException {
238                return engine.getWebLoader().loadClass(className);
239            }
240
241            @Override
242            public URL getResource(String name) {
243                return engine.getWebLoader().getResource(name);
244            }
245        };
246    }
247
248}