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