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.runtime.tomcat.dev;
020
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.lang.reflect.Method;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Timer;
031import java.util.TimerTask;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.osgi.application.FrameworkBootstrap;
036import org.nuxeo.osgi.application.MutableClassLoader;
037
038/**
039 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
040 */
041public class DevFrameworkBootstrap extends FrameworkBootstrap implements DevBundlesManager {
042
043    protected final Log log = LogFactory.getLog(DevFrameworkBootstrap.class);
044
045    protected DevBundle[] devBundles;
046
047    protected Timer bundlesCheck;
048
049    protected long lastModified = 0;
050
051    protected ReloadServiceInvoker reloadServiceInvoker;
052
053    protected File devBundlesFile;
054
055    protected final File seamdev;
056
057    protected final File webclasses;
058
059    public DevFrameworkBootstrap(MutableClassLoader cl, File home) throws IOException {
060        super(cl, home);
061        devBundlesFile = new File(home, "dev.bundles");
062        seamdev = new File(home, "nuxeo.war/WEB-INF/dev");
063        webclasses = new File(home, "nuxeo.war/WEB-INF/classes");
064    }
065
066    @Override
067    public void start() throws ReflectiveOperationException, IOException {
068        // check if we have dev. bundles or libs to deploy and add them to the
069        // classpath
070        preloadDevBundles();
071        // start the framework
072        super.start();
073        reloadServiceInvoker = new ReloadServiceInvoker((ClassLoader) loader);
074        writeComponentIndex();
075        postloadDevBundles(); // start dev bundles if any
076        String installReloadTimerOption = (String) env.get(INSTALL_RELOAD_TIMER);
077        if (installReloadTimerOption != null && Boolean.parseBoolean(installReloadTimerOption) == Boolean.TRUE) {
078            toggleTimer();
079        }
080    }
081
082    @Override
083    public void toggleTimer() {
084        // start reload timer
085        if (bundlesCheck != null) {
086            bundlesCheck.cancel();
087            bundlesCheck = null;
088        } else {
089            bundlesCheck = new Timer("Dev Bundles Loader");
090            bundlesCheck.scheduleAtFixedRate(new TimerTask() {
091                @Override
092                public void run() {
093                    loadDevBundles();
094                }
095            }, 2000, 2000);
096        }
097    }
098
099    @Override
100    public boolean isTimerRunning() {
101        return bundlesCheck != null;
102    }
103
104    @Override
105    public void stop() throws ReflectiveOperationException {
106        if (bundlesCheck != null) {
107            bundlesCheck.cancel();
108            bundlesCheck = null;
109        }
110        super.stop();
111    }
112
113    @Override
114    public String getDevBundlesLocation() {
115        return devBundlesFile.getAbsolutePath();
116    }
117
118    /**
119     * Load the development bundles and libs if any in the classpath before starting the framework.
120     */
121    protected void preloadDevBundles() throws IOException {
122        if (!devBundlesFile.isFile()) {
123            return;
124        }
125        lastModified = devBundlesFile.lastModified();
126        devBundles = DevBundle.parseDevBundleLines(new FileInputStream(devBundlesFile));
127        if (devBundles.length == 0) {
128            devBundles = null;
129            return;
130        }
131        installNewClassLoader(devBundles);
132    }
133
134    protected void postloadDevBundles() throws ReflectiveOperationException {
135        if (devBundles != null) {
136            reloadServiceInvoker.hotDeployBundles(devBundles);
137        }
138    }
139
140    @Override
141    public void loadDevBundles() {
142        long tm = devBundlesFile.lastModified();
143        if (lastModified >= tm) {
144            return;
145        }
146        lastModified = tm;
147        try {
148            reloadDevBundles(DevBundle.parseDevBundleLines(new FileInputStream(devBundlesFile)));
149        } catch (ReflectiveOperationException | IOException e) {
150            log.error("Failed to deploy dev bundles", e);
151        }
152    }
153
154    @Override
155    public void resetDevBundles(String path) {
156        devBundlesFile = new File(path);
157        lastModified = 0;
158        loadDevBundles();
159    }
160
161    @Override
162    public DevBundle[] getDevBundles() {
163        return devBundles;
164    }
165
166    protected synchronized void reloadDevBundles(DevBundle[] bundles) throws ReflectiveOperationException {
167
168        if (devBundles != null) { // clear last context
169            try {
170                reloadServiceInvoker.hotUndeployBundles(devBundles);
171                clearClassLoader();
172            } finally {
173                devBundles = null;
174            }
175        }
176
177        if (bundles != null) { // create new context
178            try {
179                installNewClassLoader(bundles);
180                reloadServiceInvoker.hotDeployBundles(bundles);
181            } finally {
182                devBundles = bundles;
183            }
184        }
185    }
186
187    protected void clearClassLoader() {
188        NuxeoDevWebappClassLoader devLoader = (NuxeoDevWebappClassLoader) loader;
189        devLoader.clear();
190        System.gc();
191    }
192
193    protected void installNewClassLoader(DevBundle[] bundles) {
194        List<URL> jarUrls = new ArrayList<URL>();
195        List<File> seamDirs = new ArrayList<File>();
196        List<File> resourceBundleFragments = new ArrayList<File>();
197        // filter dev bundles types
198        for (DevBundle bundle : bundles) {
199            if (bundle.devBundleType.isJar) {
200                try {
201                    jarUrls.add(bundle.url());
202                } catch (IOException e) {
203                    log.error("Cannot install " + bundle);
204                }
205            } else if (bundle.devBundleType == DevBundleType.Seam) {
206                seamDirs.add(bundle.file());
207            } else if (bundle.devBundleType == DevBundleType.ResourceBundleFragment) {
208                resourceBundleFragments.add(bundle.file());
209            }
210        }
211
212        // install class loader
213        NuxeoDevWebappClassLoader devLoader = (NuxeoDevWebappClassLoader) loader;
214        devLoader.createLocalClassLoader(jarUrls.toArray(new URL[jarUrls.size()]));
215
216        // install seam classes in hot sync folder
217        try {
218            installSeamClasses(seamDirs.toArray(new File[seamDirs.size()]));
219        } catch (IOException e) {
220            log.error("Cannot install seam classes in hotsync folder", e);
221        }
222
223        // install l10n resources
224        try {
225            installResourceBundleFragments(resourceBundleFragments);
226        } catch (IOException e) {
227            log.error("Cannot install l10n resources", e);
228        }
229    }
230
231    public void writeComponentIndex() {
232        File file = new File(home.getParentFile(), "sdk");
233        file.mkdirs();
234        file = new File(file, "components.index");
235        // if (file.isFile()) {
236        // return;
237        // }
238        try {
239            Method m = getClassLoader().loadClass("org.nuxeo.runtime.model.impl.ComponentRegistrySerializer").getMethod(
240                    "writeIndex", File.class);
241            m.invoke(null, file);
242        } catch (ReflectiveOperationException t) {
243            // ignore
244        }
245    }
246
247    public void installSeamClasses(File[] dirs) throws IOException {
248        if (seamdev.exists()) {
249            IOUtils.deleteTree(seamdev);
250        }
251        seamdev.mkdirs();
252        for (File dir : dirs) {
253            IOUtils.copyTree(dir, seamdev);
254        }
255    }
256
257    public void installResourceBundleFragments(List<File> files) throws IOException {
258        Map<String, List<File>> fragments = new HashMap<String, List<File>>();
259
260        for (File file : files) {
261            String name = resourceBundleName(file);
262            if (!fragments.containsKey(name)) {
263                fragments.put(name, new ArrayList<File>());
264            }
265            fragments.get(name).add(file);
266        }
267        for (String name : fragments.keySet()) {
268            IOUtils.appendResourceBundleFragments(name, fragments.get(name), webclasses);
269        }
270    }
271
272    protected static String resourceBundleName(File file) {
273        String name = file.getName();
274        return name.substring(name.lastIndexOf('-') + 1);
275    }
276
277}