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.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}