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