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.getPropertyValue(desc);
208      }
209    
210      /**
211       * Set a context property to a new value. If the provided value is null, then the property is removed.
212       *
213       * @param desc the property descriptor
214       * @param value the property value
215       * @param <T> the property parameter type
216       * @throws NullPointerException if the descriptor argument is null
217       */
218      public <T> void setProperty(PropertyDescriptor<T> desc, T value) throws NullPointerException {
219        propertyManager.setProperty(desc, value);
220      }
221    
222      /**
223       * Set a context property to a new value. If the provided value is null, then the property is removed.
224       *
225       * @param desc the property descriptor
226       * @param value the property value
227       * @param <T> the property parameter type
228       * @throws NullPointerException if the descriptor argument is null
229       * @throws IllegalArgumentException if the string value cannot be converted to the property type
230       */
231      public <T> void setProperty(PropertyDescriptor<T> desc, String value) throws NullPointerException, IllegalArgumentException {
232        propertyManager.parseProperty(desc, value);
233      }
234    
235      /**
236       * Load a resource from the context.
237       *
238       * @param resourceId the resource id
239       * @param resourceKind the resource kind
240       * @return the resource or null if it cannot be found
241       */
242      public Resource loadResource(String resourceId, ResourceKind resourceKind) {
243        return Utils.first(resourceManager.loadResource(resourceId, resourceKind));
244      }
245    
246      /**
247       * Load a resource from the context.
248       *
249       * @param resourceId the resource id
250       * @param resourceKind the resource kind
251       * @return the resource or null if it cannot be found
252       */
253      public Iterable<Resource> loadResources(String resourceId, ResourceKind resourceKind) {
254        return resourceManager.loadResource(resourceId, resourceKind);
255      }
256    
257      /**
258       * List the resources id for a specific resource kind.
259       *
260       * @param kind the resource kind
261       * @return the resource ids
262       */
263      public Iterable<String> listResources(ResourceKind kind) {
264        return resourceManager.listResourceId(kind);
265      }
266    
267      /**
268       * Returns the classloader associated with this context.
269       *
270       * @return the class loader
271       */
272      public ClassLoader getLoader() {
273        return loader;
274      }
275    
276      public Iterable<CRaSHPlugin<?>> getPlugins() {
277        return manager.getPlugins();
278      }
279    
280      /**
281       * Returns the plugins associated with this context.
282       *
283       * @param pluginType the plugin type
284       * @param <T> the plugin generic type
285       * @return the plugins
286       */
287      public <T> Iterable<T> getPlugins(Class<T> pluginType) {
288        return manager.getPlugins(pluginType);
289      }
290    
291      /**
292       * Returns the first plugin associated with this context implementing the specified type.
293       *
294       * @param pluginType the plugin type
295       * @param <T> the plugin generic type
296       * @return the plugins
297       */
298      public <T> T getPlugin(Class<T> pluginType) {
299        Iterator<T> plugins = manager.getPlugins(pluginType).iterator();
300        return plugins.hasNext() ? plugins.next() : null;
301      }
302    
303      /**
304       * Refresh the fs system view. This is normally triggered by the periodic job but it can be manually
305       * invoked to trigger explicit refreshes.
306       */
307      public void refresh() {
308        resourceManager.refresh();
309      }
310    
311      synchronized void start() {
312        if (!started) {
313    
314          // Start refresh
315          Integer refreshRate = getProperty(PropertyDescriptor.VFS_REFRESH_PERIOD);
316          TimeUnit timeUnit = getProperty(PropertyDescriptor.VFS_REFRESH_UNIT);
317          if (refreshRate != null && refreshRate > 0) {
318            TimeUnit tu = timeUnit != null ? timeUnit : TimeUnit.SECONDS;
319            scannerFuture = scanner.scheduleWithFixedDelay(new Runnable() {
320              public void run() {
321                refresh();
322              }
323            }, 0, refreshRate, tu);
324          }
325    
326          // Init plugins
327          manager.getPlugins(Object.class);
328    
329          //
330          started = true;
331        } else {
332          log.log(Level.WARNING, "Attempt to double start");
333        }
334      }
335    
336      synchronized void stop() {
337    
338        //
339        if (started) {
340    
341          // Shutdown manager
342          manager.shutdown();
343    
344          // Shutdown scanner
345          if (scannerFuture != null) {
346            scannerFuture.cancel(true);
347          }
348    
349          //
350          scanner.shutdownNow();
351    
352          // Shutdown executor
353          executor.shutdownNow();
354        } else {
355          log.log(Level.WARNING, "Attempt to stop when stopped");
356        }
357      }
358    }