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 }