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 */
019package org.crsh.plugin;
020
021import org.crsh.util.Utils;
022import org.crsh.vfs.FS;
023import org.crsh.vfs.Resource;
024
025import java.io.InputStream;
026import java.util.*;
027import java.util.concurrent.ExecutorService;
028import java.util.concurrent.Executors;
029import java.util.concurrent.ScheduledExecutorService;
030import java.util.concurrent.ScheduledFuture;
031import java.util.concurrent.ScheduledThreadPoolExecutor;
032import java.util.concurrent.TimeUnit;
033import java.util.logging.Level;
034import java.util.logging.Logger;
035
036public 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}