001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.plugins;
019
020import io.stallion.boot.StallionRunAction;
021import io.stallion.plugins.javascript.JsPluginEngine;
022
023import io.stallion.plugins.javascript.TestResults;
024import io.stallion.services.Log;
025import org.apache.commons.lang3.StringUtils;
026import org.apache.xbean.classloader.JarFileClassLoader;
027
028import java.io.File;
029import java.net.URL;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.Map;
033import java.util.jar.JarFile;
034import java.util.jar.Manifest;
035
036import static io.stallion.Context.*;
037import static io.stallion.utils.Literals.*;
038
039/**
040 * Registry Service that loads plugins, boots them, and keeps track of them.
041 *
042 */
043public class PluginRegistry {
044
045
046    private JarFileClassLoader classLoader;
047
048    private Map<String, StallionJavaPlugin> javaPluginByName = map();
049    private Map<String, JsPluginEngine> jsPlugins = map();
050
051    private static PluginRegistry _instance;
052
053    public static PluginRegistry instance() {
054
055        return _instance;
056    }
057
058    public static PluginRegistry loadWithJavaPlugins(String targetPath) {
059        if (_instance == null) {
060            _instance = new PluginRegistry();
061            _instance.loadJarPlugins(targetPath);
062        }
063        return _instance;
064    }
065
066    public static PluginRegistry loadWithJavaPlugins(String targetPath, StallionJavaPlugin ...extraPlugins) {
067        if (_instance == null) {
068            _instance = new PluginRegistry();
069            _instance.loadJarPlugins(targetPath);
070            for (StallionJavaPlugin plugin: extraPlugins) {
071                _instance.loadPluginFromBooter(plugin);
072            }
073        }
074        return _instance;
075    }
076
077    PluginRegistry() {
078        //new JarFileClassLoader()
079        this.classLoader = new JarFileClassLoader(
080                "PluginClassLoader" + System.currentTimeMillis(),
081                new URL[]{},
082                PluginRegistry.class.getClassLoader()
083        );
084    }
085
086    /*
087    public static PluginRegistry load() {
088        return PluginRegistry.load(new ArrayList<StallionPlugin>());
089    }
090
091    public static PluginRegistry load(List<StallionPlugin> extraPlugins) {
092        _instance = new PluginRegistry();
093        try {
094
095            if (extraPlugins != null) {
096                for(StallionPlugin booter: extraPlugins) {
097                    _instance.loadPluginFromBooter(booter);
098                }
099            }
100            _instance.load(true);
101        } catch (Exception e) {
102            throw new RuntimeException(e);
103        }
104        return _instance;
105    }
106    */
107
108    public static void shutdown() {
109        if (_instance != null) {
110            for (JsPluginEngine engine: _instance.jsPlugins.values()) {
111                engine.shutdown();
112            }
113            for (StallionJavaPlugin plugin: _instance.getJavaPluginByName().values()) {
114                plugin.shutdown();
115            }
116        }
117        _instance = null;
118    }
119
120
121
122   // public void load(Boolean shouldWatchFiles) throws Exception {
123   //     loadPlugins(shouldWatchFiles);
124    //}
125
126    public List<StallionRunAction> getAllPluginDefinedStallionRunActions() {
127        List<StallionRunAction> actions = list();
128        for (StallionJavaPlugin plugin: getJavaPluginByName().values()) {
129            actions.addAll(plugin.getActions());
130        }
131        return actions;
132    }
133
134    public void loadAndRunJavascriptPlugins() {
135        loadAndRunJavascriptPlugins(true);
136    }
137
138    public void loadAndRunJavascriptPlugins(Boolean shouldWatchFiles) {
139        try {
140            doLoadAndRunJavascriptPlugins(shouldWatchFiles);
141        } catch (Exception e) {
142            throw new RuntimeException(e);
143        }
144    }
145
146    private void doLoadAndRunJavascriptPlugins(Boolean shouldWatchFiles) throws Exception {
147
148
149        // Load the main javascript file, if it exists
150        String jsMainPath = settings().getTargetFolder() + "/js/main.js";
151        File jsMain = new File(jsMainPath);
152        if (jsMain.exists() && jsMain.isFile()) {
153            JsPluginEngine engine = new JsPluginEngine(shouldWatchFiles);
154            jsPlugins.put("main.js", engine);
155            engine.loadJavascript("main.js", jsMainPath);
156        }
157
158
159        String path = settings().getTargetFolder() + "/plugins";
160
161        File folder = new File(path);
162        if (!folder.exists() || !folder.isDirectory()) {
163            return;
164        }
165        File[] children = folder.listFiles();
166        if (children != null) {
167            for (File child : children) {
168                if (child.isDirectory()) {
169
170                    loadScriptPlugin(child.getAbsolutePath(), child.getName(), shouldWatchFiles);
171                }
172            }
173        }
174
175    }
176
177    private void loadScriptPlugin(String folderPath, String pluginName, Boolean shouldWatchFiles) throws Exception {
178        //PluginDefinition definition = new Toml().parse(pluginTomlFile).to(PluginDefinition.class);
179        Log.info("Load plugin {0} name:{1}", folderPath, pluginName);
180
181        String jsPath = folderPath + "/plugin.js";
182        File jsFile = new File(jsPath);
183        if (jsFile.exists() && jsFile.isFile()) {
184            JsPluginEngine engine = new JsPluginEngine(shouldWatchFiles);
185            jsPlugins.put(pluginName, engine);
186            engine.loadJavascript(pluginName, jsPath);
187
188        }
189
190
191    }
192
193    public void loadJarPlugins(String targetPath) {
194        try {
195            doLoadJarPlugins(targetPath);
196        } catch (Exception e) {
197            throw new RuntimeException(e);
198        }
199
200    }
201    private void doLoadJarPlugins(String targetPath) throws Exception {
202        String jarFolderPath = targetPath + "/jars";
203        Log.fine("Loading jars at {0}", jarFolderPath);
204        File jarFolder = new File(jarFolderPath);
205        if (!jarFolder.exists() || !jarFolder.isDirectory()) {
206            Log.fine("No jar folder exists at {0}. No jar plugins will be loaded.", jarFolderPath);
207            return;
208        }
209        File[] files = jarFolder.listFiles();
210        ArrayList<URL> urls = new ArrayList<URL>();
211        for (int i = 0; i < files.length; i++) {
212            if (files[i].getName().endsWith(".jar")) {
213                //urls.add(files[i].toURL());
214                Log.fine("Loading plugin jar {0}", files[i].toURI());
215                urls.add(files[i].toURI().toURL());
216            }
217        }
218
219        String pluginBooterClass = "";
220
221        for (URL jarUrl: urls) {
222            // Add jars to the class loader
223            getClassLoader().addURL(jarUrl);
224        }
225
226        for (URL jarUrl: urls) {
227            // Load the booter class
228            Manifest m = new JarFile(jarUrl.getFile()).getManifest();
229            Log.finer("Found manifest for jar {0}", jarUrl);
230            if (!StringUtils.isEmpty(m.getMainAttributes().getValue("pluginBooterClass"))) {
231                pluginBooterClass = m.getMainAttributes().getValue("pluginBooterClass");
232            }
233            if (empty(pluginBooterClass)) {
234                continue;
235            }
236            Log.fine("Load plugin class {0} from jar={1}", pluginBooterClass, jarUrl);
237            Class booterClass = getClassLoader().loadClass(pluginBooterClass);
238            Log.finer("Booter class was loaded from: {0} ", booterClass.getProtectionDomain().getCodeSource().getLocation());
239            StallionJavaPlugin booter = (StallionJavaPlugin)booterClass.newInstance();
240            loadPluginFromBooter(booter);
241        }
242    }
243
244    /**
245     * Boot all jar plugins that have already been loaded.
246     *
247     */
248     public void bootJarPlugins()  {
249        for(StallionJavaPlugin plugin: getJavaPluginByName().values()) {
250            try {
251                plugin.boot();
252            } catch (Exception e) {
253                throw new RuntimeException(e);
254            }
255        }
256    }
257
258    public void loadPluginFromBooter(StallionJavaPlugin booter) {
259        Log.info("Load plugin {0} from class {1}", booter.getPluginName(), booter.getClass().getName());
260        if (getJavaPluginByName().containsKey(booter.getPluginName())) {
261            Log.warn("Plugin already loaded, skipping {0}", booter.getPluginName());
262            return;
263        }
264        booter.setPluginRegistry(this);
265        getJavaPluginByName().put(booter.getPluginName(), booter);
266    }
267
268    public List<TestResults> runJsTests(String plugin, String jsFile) {
269        return jsPlugins.get(plugin).runTestsInFile(jsFile);
270    }
271
272    public JsPluginEngine getEngine(String plugin) {
273        return jsPlugins.getOrDefault(plugin, null);
274    }
275
276
277    public JarFileClassLoader getClassLoader() {
278        return classLoader;
279    }
280
281    public void setClassLoader(JarFileClassLoader classLoader) {
282        this.classLoader = classLoader;
283    }
284
285
286    public Map<String, StallionJavaPlugin> getJavaPluginByName() {
287        return javaPluginByName;
288    }
289}