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