001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software 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     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    package org.crsh.plugin;
020    
021    import org.crsh.util.Utils;
022    import org.crsh.vfs.FS;
023    import org.crsh.vfs.Resource;
024    
025    import java.io.InputStream;
026    import java.util.*;
027    import java.util.concurrent.ExecutorService;
028    import java.util.concurrent.Executors;
029    import java.util.concurrent.ScheduledExecutorService;
030    import java.util.concurrent.ScheduledFuture;
031    import java.util.concurrent.ScheduledThreadPoolExecutor;
032    import java.util.concurrent.TimeUnit;
033    import java.util.logging.Level;
034    import java.util.logging.Logger;
035    
036    public final class PluginContext {
037    
038      /** . */
039      private static final Logger log = Logger.getLogger(PluginContext.class.getName());
040    
041      /** . */
042      final PluginManager manager;
043    
044      /** . */
045      private final ClassLoader loader;
046    
047      /** . */
048      private final String version;
049    
050      /** . */
051      private final ScheduledExecutorService scanner;
052    
053      /** . */
054      private final Map<String, Object> attributes;
055    
056      /** The shared executor. */
057      private final ExecutorService executor;
058    
059      /** . */
060      private boolean started;
061    
062      /** . */
063      private ScheduledFuture scannerFuture;
064    
065      /** . */
066      private final ResourceManager resourceManager;
067    
068      /** . */
069      private final PropertyManager propertyManager;
070    
071      /**
072       * Create a new plugin context with preconfigured executor and scanner, this is equivalent to invoking:
073       *
074       * <code><pre>new PluginContext(
075       *    Executors.newFixedThreadPool(20),
076       *    new ScheduledThreadPoolExecutor(1),
077       *    discovery,
078       *    attributes,
079       *    cmdFS,
080       *    confFS,
081       *    loader);</pre></code>
082       *
083       * @param discovery the plugin discovery
084       * @param cmdFS the command file system
085       * @param attributes the attributes
086       * @param confFS the conf file system
087       * @param loader the loader
088       * @throws NullPointerException if any parameter argument is null
089       */
090      public PluginContext(
091          PluginDiscovery discovery,
092          Map<String, Object> attributes,
093          FS cmdFS,
094          FS confFS,
095          ClassLoader loader) throws NullPointerException {
096        this(
097            Executors.newFixedThreadPool(20),
098            new ScheduledThreadPoolExecutor(1),
099            discovery,
100            attributes,
101            cmdFS,
102            confFS,
103            loader);
104      }
105    
106      /**
107       * Create a new plugin context.
108       *
109       * @param executor the executor for executing asynchronous jobs
110       * @param scanner the background scanner for scanning commands
111       * @param discovery the plugin discovery
112       * @param cmdFS the command file system
113       * @param attributes the attributes
114       * @param confFS the conf file system
115       * @param loader the loader
116       * @throws NullPointerException if any parameter argument is null
117       */
118      public PluginContext(
119        ExecutorService executor,
120        ScheduledExecutorService scanner,
121        PluginDiscovery discovery,
122        Map<String, Object> attributes,
123        FS cmdFS,
124        FS confFS,
125        ClassLoader loader) throws NullPointerException {
126        if (executor == null) {
127          throw new NullPointerException("No null executor accepted");
128        }
129        if (scanner == null) {
130          throw new NullPointerException("No null scanner accepted");
131        }
132        if (discovery == null) {
133          throw new NullPointerException("No null plugin discovery accepted");
134        }
135        if (confFS == null) {
136          throw new NullPointerException("No null configuration file system accepted");
137        }
138        if (cmdFS == null) {
139          throw new NullPointerException("No null command file system accepted");
140        }
141        if (loader == null) {
142          throw new NullPointerException("No null loader accepted");
143        }
144        if (attributes == null) {
145          throw new NullPointerException("No null attributes accepted");
146        }
147    
148        //
149        String version = null;
150        try {
151          Properties props = new Properties();
152          InputStream in = getClass().getClassLoader().getResourceAsStream("META-INF/maven/org.crashub/crash.shell/pom.properties");
153          if (in != null) {
154            props.load(in);
155            version = props.getProperty("version");
156          }
157        } catch (Exception e) {
158          log.log(Level.SEVERE, "Could not load maven properties", e);
159        }
160    
161        //
162        if (version == null) {
163          log.log(Level.WARNING, "No version found will use unknown value instead");
164          version = "unknown";
165        }
166    
167        //
168        this.loader = loader;
169        this.attributes = attributes;
170        this.version = version;
171        this.started = false;
172        this.manager = new PluginManager(this, discovery);
173        this.executor = executor;
174        this.scanner = scanner;
175        this.resourceManager = new ResourceManager(cmdFS, confFS);
176        this.propertyManager = new PropertyManager();
177      }
178    
179      public String getVersion() {
180        return version;
181      }
182    
183      public Map<String, Object> getAttributes() {
184        return attributes;
185      }
186    
187      public ExecutorService getExecutor() {
188        return executor;
189      }
190    
191      /**
192       * @return the property manager
193       */
194      public PropertyManager getPropertyManager() {
195        return propertyManager;
196      }
197    
198      /**
199       * Returns a context property or null if it cannot be found.
200       *
201       * @param desc the property descriptor
202       * @param <T> the property parameter type
203       * @return the property value
204       * @throws NullPointerException if the descriptor argument is null
205       */
206      public <T> T getProperty(PropertyDescriptor<T> desc) throws NullPointerException {
207        return propertyManager.resolvePropertyValue(desc);
208      }
209    
210      /**
211       * Returns a context property or null if it cannot be found.
212       *
213       * @param propertyName the name of the property
214       * @param type the property type
215       * @param <T> the property parameter type
216       * @return the property value
217       * @throws NullPointerException if the descriptor argument is null
218       */
219      public <T> T getProperty(String propertyName, Class<T> type) throws NullPointerException {
220        return propertyManager.resolvePropertyValue(propertyName, type);
221      }
222    
223      /**
224       * Set a context property to a new value. If the provided value is null, then the property is removed.
225       *
226       * @param desc the property descriptor
227       * @param value the property value
228       * @param <T> the property parameter type
229       * @throws NullPointerException if the descriptor argument is null
230       */
231      public <T> void setProperty(PropertyDescriptor<T> desc, T value) throws NullPointerException {
232        propertyManager.setProperty(desc, value);
233      }
234    
235      /**
236       * Set a context property to a new value. If the provided value is null, then the property is removed.
237       *
238       * @param desc the property descriptor
239       * @param value the property value
240       * @param <T> the property parameter type
241       * @throws NullPointerException if the descriptor argument is null
242       * @throws IllegalArgumentException if the string value cannot be converted to the property type
243       */
244      public <T> void setProperty(PropertyDescriptor<T> desc, String value) throws NullPointerException, IllegalArgumentException {
245        propertyManager.parseProperty(desc, value);
246      }
247    
248      /**
249       * Load a resource from the context.
250       *
251       * @param resourceId the resource id
252       * @param resourceKind the resource kind
253       * @return the resource or null if it cannot be found
254       */
255      public Resource loadResource(String resourceId, ResourceKind resourceKind) {
256        return Utils.first(resourceManager.loadResource(resourceId, resourceKind));
257      }
258    
259      /**
260       * Load a resource from the context.
261       *
262       * @param resourceId the resource id
263       * @param resourceKind the resource kind
264       * @return the resource or null if it cannot be found
265       */
266      public Iterable<Resource> loadResources(String resourceId, ResourceKind resourceKind) {
267        return resourceManager.loadResource(resourceId, resourceKind);
268      }
269    
270      /**
271       * List the resources id for a specific resource kind.
272       *
273       * @param kind the resource kind
274       * @return the resource ids
275       */
276      public Iterable<String> listResources(ResourceKind kind) {
277        return resourceManager.listResourceId(kind);
278      }
279    
280      /**
281       * Returns the classloader associated with this context.
282       *
283       * @return the class loader
284       */
285      public ClassLoader getLoader() {
286        return loader;
287      }
288    
289      public Iterable<CRaSHPlugin<?>> getPlugins() {
290        return manager.getPlugins();
291      }
292    
293      /**
294       * Returns the plugins associated with this context.
295       *
296       * @param pluginType the plugin type
297       * @param <T> the plugin generic type
298       * @return the plugins
299       */
300      public <T> Iterable<T> getPlugins(Class<T> pluginType) {
301        return manager.getPlugins(pluginType);
302      }
303    
304      /**
305       * Returns the first plugin associated with this context implementing the specified type.
306       *
307       * @param pluginType the plugin type
308       * @param <T> the plugin generic type
309       * @return the plugins
310       */
311      public <T> T getPlugin(Class<T> pluginType) {
312        Iterator<T> plugins = manager.getPlugins(pluginType).iterator();
313        return plugins.hasNext() ? plugins.next() : null;
314      }
315    
316      /**
317       * Refresh the fs system view. This is normally triggered by the periodic job but it can be manually
318       * invoked to trigger explicit refreshes.
319       */
320      public void refresh() {
321        resourceManager.refresh();
322      }
323    
324      synchronized void start() {
325        if (!started) {
326    
327          // Start refresh
328          Integer refreshRate = getProperty(PropertyDescriptor.VFS_REFRESH_PERIOD);
329          TimeUnit timeUnit = getProperty(PropertyDescriptor.VFS_REFRESH_UNIT);
330          if (refreshRate != null && refreshRate > 0) {
331            TimeUnit tu = timeUnit != null ? timeUnit : TimeUnit.SECONDS;
332            scannerFuture = scanner.scheduleWithFixedDelay(new Runnable() {
333              public void run() {
334                refresh();
335              }
336            }, 0, refreshRate, tu);
337          }
338    
339          // Init plugins
340          manager.getPlugins(Object.class);
341    
342          //
343          started = true;
344        } else {
345          log.log(Level.WARNING, "Attempt to double start");
346        }
347      }
348    
349      synchronized void stop() {
350    
351        //
352        if (started) {
353    
354          // Shutdown manager
355          manager.shutdown();
356    
357          // Shutdown scanner
358          if (scannerFuture != null) {
359            scannerFuture.cancel(true);
360          }
361    
362          //
363          scanner.shutdownNow();
364    
365          // Shutdown executor
366          executor.shutdownNow();
367        } else {
368          log.log(Level.WARNING, "Attempt to stop when stopped");
369        }
370      }
371    }