001 /*
002 * Copyright (C) 2003-2009 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.File;
023 import org.crsh.vfs.Path;
024 import org.crsh.vfs.Resource;
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027
028 import java.io.ByteArrayOutputStream;
029 import java.io.IOException;
030 import java.io.InputStream;
031 import java.util.*;
032 import java.util.concurrent.ExecutorService;
033 import java.util.concurrent.Executors;
034 import java.util.concurrent.ScheduledExecutorService;
035 import java.util.concurrent.ScheduledThreadPoolExecutor;
036 import java.util.concurrent.TimeUnit;
037 import java.util.regex.Matcher;
038 import java.util.regex.Pattern;
039
040 /**
041 * The plugin context.
042 *
043 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
044 * @version $Revision$
045 */
046 public final class PluginContext {
047
048 /** . */
049 private static final Pattern p = Pattern.compile("(.+)\\.groovy");
050
051 /** . */
052 private static final Logger log = LoggerFactory.getLogger(PluginContext.class);
053
054 /** . */
055 final PluginManager manager;
056
057 /** . */
058 private final ClassLoader loader;
059
060 /** . */
061 private final String version;
062
063 /** . */
064 private ScheduledExecutorService scanner;
065
066 /** . */
067 private volatile List<File> dirs;
068
069 /** . */
070 private final Map<PropertyDescriptor<?>, Property<?>> properties;
071
072 /** . */
073 private final FS cmdFS;
074
075 /** . */
076 private final Map<String, Object> attributes;
077
078 /** . */
079 private final FS confFS;
080
081 /** . */
082 private boolean started;
083
084 /** The shared executor. */
085 private ExecutorService executor;
086
087
088 /**
089 * Create a new plugin context.
090 *
091 * @param discovery the plugin discovery
092 * @param cmdFS the command file system
093 * @param attributes the attributes
094 * @param confFS the conf file system
095 * @param loader the loader
096 * @throws NullPointerException if any parameter argument is null
097 */
098 public PluginContext(
099 PluginDiscovery discovery,
100 Map<String, Object> attributes,
101 FS cmdFS,
102 FS confFS,
103 ClassLoader loader) throws NullPointerException {
104 if (discovery == null) {
105 throw new NullPointerException("No null plugin disocovery accepted");
106 }
107 if (confFS == null) {
108 throw new NullPointerException("No null configuration file system accepted");
109 }
110 if (cmdFS == null) {
111 throw new NullPointerException("No null command file system accepted");
112 }
113 if (loader == null) {
114 throw new NullPointerException();
115 }
116 if (attributes == null) {
117 throw new NullPointerException();
118 }
119
120 //
121 String version = null;
122 try {
123 Properties props = new Properties();
124 InputStream in = getClass().getClassLoader().getResourceAsStream("META-INF/maven/org.crsh/crsh.shell.core/pom.properties");
125 if (in != null) {
126 props.load(in);
127 version = props.getProperty("version");
128 }
129 } catch (Exception e) {
130 log.error("Could not load maven properties", e);
131 }
132
133 //
134 if (version == null) {
135 log.warn("No version found will use unknown value instead");
136 version = "unknown";
137 }
138
139 //
140 this.loader = loader;
141 this.attributes = attributes;
142 this.version = version;
143 this.dirs = Collections.emptyList();
144 this.cmdFS = cmdFS;
145 this.properties = new HashMap<PropertyDescriptor<?>, Property<?>>();
146 this.started = false;
147 this.manager = new PluginManager(this, discovery);
148 this.confFS = confFS;
149 this.executor = Executors.newFixedThreadPool(20);
150 }
151
152 public String getVersion() {
153 return version;
154 }
155
156 public Map<String, Object> getAttributes() {
157 return attributes;
158 }
159
160 public ExecutorService getExecutor() {
161 return executor;
162 }
163
164 /**
165 * Returns a context property or null if it cannot be found.
166 *
167 * @param desc the property descriptor
168 * @param <T> the property parameter type
169 * @return the property value
170 * @throws NullPointerException if the descriptor argument is null
171 */
172 public <T> T getProperty(PropertyDescriptor<T> desc) throws NullPointerException {
173 if (desc == null) {
174 throw new NullPointerException();
175 }
176 @SuppressWarnings("unchecked")
177 Property<T> property = (Property<T>)properties.get(desc);
178 return property != null ? property.getValue() : desc.defaultValue;
179 }
180
181 /**
182 * Returns a context property or null if it cannot be found.
183 *
184 * @param propertyName the name of the property
185 * @param type the property type
186 * @param <T> the property parameter type
187 * @return the property value
188 * @throws NullPointerException if the descriptor argument is null
189 */
190 public <T> T getProperty(String propertyName, Class<T> type) throws NullPointerException {
191 if (propertyName == null) {
192 throw new NullPointerException("No null property name accepted");
193 }
194 if (type == null) {
195 throw new NullPointerException("No null property type accepted");
196 }
197 for (PropertyDescriptor<?> pd : properties.keySet())
198 {
199 if (pd.name.equals(propertyName) && type.isAssignableFrom(pd.type))
200 {
201 return type.cast(getProperty(pd));
202 }
203 }
204 return null;
205 }
206
207 /**
208 * Set a context property to a new value. If the provided value is null, then the property is removed.
209 *
210 * @param desc the property descriptor
211 * @param value the property value
212 * @param <T> the property parameter type
213 * @throws NullPointerException if the descriptor argument is null
214 */
215 public <T> void setProperty(PropertyDescriptor<T> desc, T value) throws NullPointerException {
216 if (desc == null) {
217 throw new NullPointerException();
218 }
219 if (value == null) {
220 log.debug("Removing property " + desc.name);
221 properties.remove(desc);
222 } else {
223 Property<T> property = new Property<T>(desc, value);
224 log.debug("Setting property " + desc.name + " to value " + property.getValue());
225 properties.put(desc, property);
226 }
227 }
228
229 /**
230 * Set a context property to a new value. If the provided value is null, then the property is removed.
231 *
232 * @param desc the property descriptor
233 * @param value the property value
234 * @param <T> the property parameter type
235 * @throws NullPointerException if the descriptor argument is null
236 * @throws IllegalArgumentException if the string value cannot be converted to the property type
237 */
238 public <T> void setProperty(PropertyDescriptor<T> desc, String value) throws NullPointerException, IllegalArgumentException {
239 if (desc == null) {
240 throw new NullPointerException();
241 }
242 if (value == null) {
243 log.debug("Removing property " + desc.name);
244 properties.remove(desc);
245 } else {
246 Property<T> property = desc.toProperty(value);
247 log.debug("Setting property " + desc.name + " to value " + property.getValue());
248 properties.put(desc, property);
249 }
250 }
251
252 /**
253 * Load a resource from the context.
254 *
255 * @param resourceId the resource id
256 * @param resourceKind the resource kind
257 * @return the resource or null if it cannot be found
258 */
259 public Resource loadResource(String resourceId, ResourceKind resourceKind) {
260 Resource res = null;
261 try {
262
263 //
264 switch (resourceKind) {
265 case LIFECYCLE:
266 if ("login".equals(resourceId) || "logout".equals(resourceId)) {
267 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
268 long timestamp = Long.MIN_VALUE;
269 for (File path : dirs) {
270 File f = path.child(resourceId + ".groovy", false);
271 if (f != null) {
272 Resource sub = f.getResource();
273 if (sub != null) {
274 buffer.write(sub.getContent());
275 buffer.write('\n');
276 timestamp = Math.max(timestamp, sub.getTimestamp());
277 }
278 }
279 }
280 return new Resource(buffer.toByteArray(), timestamp);
281 }
282 break;
283 case COMMAND:
284 // Find the resource first, we find for the first found
285 for (File path : dirs) {
286 File f = path.child(resourceId + ".groovy", false);
287 if (f != null) {
288 res = f.getResource();
289 }
290 }
291 break;
292 case CONFIG:
293 String path = "/" + resourceId;
294 File file = confFS.get(Path.get(path));
295 if (file != null) {
296 res = file.getResource();
297 }
298 }
299 } catch (IOException e) {
300 log.warn("Could not obtain resource " + resourceId, e);
301 }
302 return res;
303 }
304
305 /**
306 * List the resources id for a specific resource kind.
307 *
308 * @param kind the resource kind
309 * @return the resource ids
310 */
311 public List<String> listResourceId(ResourceKind kind) {
312 switch (kind) {
313 case COMMAND:
314 SortedSet<String> all = new TreeSet<String>();
315 try {
316 for (File path : dirs) {
317 for (File file : path.children()) {
318 String name = file.getName();
319 Matcher matcher = p.matcher(name);
320 if (matcher.matches()) {
321 all.add(matcher.group(1));
322 }
323 }
324 }
325 }
326 catch (IOException e) {
327 e.printStackTrace();
328 }
329 all.remove("login");
330 all.remove("logout");
331 return new ArrayList<String>(all);
332 default:
333 return Collections.emptyList();
334 }
335 }
336
337 /**
338 * Returns the classloader associated with this context.
339 *
340 * @return the class loader
341 */
342 public ClassLoader getLoader() {
343 return loader;
344 }
345
346 public Iterable<CRaSHPlugin<?>> getPlugins() {
347 return manager.getPlugins();
348 }
349
350 /**
351 * Returns the plugins associated with this context.
352 *
353 * @param pluginType the plugin type
354 * @param <T> the plugin generic type
355 * @return the plugins
356 */
357 public <T> Iterable<T> getPlugins(Class<T> pluginType) {
358 return manager.getPlugins(pluginType);
359 }
360
361 /**
362 * Returns the first plugin associated with this context implementing the specified type.
363 *
364 * @param pluginType the plugin type
365 * @param <T> the plugin generic type
366 * @return the plugins
367 */
368 public <T> T getPlugin(Class<T> pluginType) {
369 Iterator<T> plugins = manager.getPlugins(pluginType).iterator();
370 return plugins.hasNext() ? plugins.next() : null;
371 }
372
373 /**
374 * Refresh the fs system view. This is normally triggered by the periodic job but it can be manually
375 * invoked to trigger explicit refreshes.
376 */
377 public void refresh() {
378 try {
379 File commands = cmdFS.get(Path.get("/"));
380 List<File> newDirs = new ArrayList<File>();
381 newDirs.add(commands);
382 for (File path : commands.children()) {
383 if (path.isDir()) {
384 newDirs.add(path);
385 }
386 }
387 dirs = newDirs;
388 }
389 catch (IOException e) {
390 e.printStackTrace();
391 }
392 }
393
394 synchronized void start() {
395 if (!started) {
396
397 // Start refresh
398 Integer refreshRate = getProperty(PropertyDescriptor.VFS_REFRESH_PERIOD);
399 TimeUnit timeUnit = getProperty(PropertyDescriptor.VFS_REFRESH_UNIT);
400 if (refreshRate != null && refreshRate > 0) {
401 TimeUnit tu = timeUnit != null ? timeUnit : TimeUnit.SECONDS;
402 scanner = new ScheduledThreadPoolExecutor(1);
403 scanner.scheduleWithFixedDelay(new Runnable() {
404 public void run() {
405 refresh();
406 }
407 }, 0, refreshRate, tu);
408 }
409
410 // Init plugins
411 manager.getPlugins(Object.class);
412
413 //
414 started = true;
415 } else {
416 log.warn("Attempt to double start");
417 }
418 }
419
420 synchronized void stop() {
421
422 //
423 if (started) {
424
425 // Shutdown manager
426 manager.shutdown();
427
428 // Shutdown scanner
429 if (scanner != null) {
430 ScheduledExecutorService tmp = scanner;
431 scanner = null;
432 tmp.shutdownNow();
433 }
434
435 // Shutdown executor
436 executor.shutdownNow();
437 } else {
438 log.warn("Attempt to stop when stopped");
439 }
440 }
441 }