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, ?> 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, ?> 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 Iterable<CRaSHPlugin<?>> getPlugins() { 153 return manager.getPlugins(); 154 } 155 156 public String getVersion() { 157 return version; 158 } 159 160 public Map<String, ?> getAttributes() { 161 return attributes; 162 } 163 164 public ExecutorService getExecutor() { 165 return executor; 166 } 167 168 /** 169 * Returns a context property or null if it cannot be found. 170 * 171 * @param desc the property descriptor 172 * @param <T> the property parameter type 173 * @return the property value 174 * @throws NullPointerException if the descriptor argument is null 175 */ 176 public <T> T getProperty(PropertyDescriptor<T> desc) throws NullPointerException { 177 if (desc == null) { 178 throw new NullPointerException(); 179 } 180 Property<T> property = (Property<T>)properties.get(desc); 181 return property != null ? property.getValue() : desc.defaultValue; 182 } 183 184 /** 185 * Returns a context property or null if it cannot be found. 186 * 187 * @param propertyName the name of the property 188 * @param type the property type 189 * @param <T> the property parameter type 190 * @return the property value 191 * @throws NullPointerException if the descriptor argument is null 192 */ 193 public <T> T getProperty(String propertyName, Class<T> type) throws NullPointerException { 194 if (propertyName == null) { 195 throw new NullPointerException("No null property name accepted"); 196 } 197 if (type == null) { 198 throw new NullPointerException("No null property type accepted"); 199 } 200 for (PropertyDescriptor<?> pd : properties.keySet()) 201 { 202 if (pd.name.equals(propertyName) && type.isAssignableFrom(pd.type)) 203 { 204 return type.cast(getProperty(pd)); 205 } 206 } 207 return null; 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 if (desc == null) { 220 throw new NullPointerException(); 221 } 222 if (value == null) { 223 log.debug("Removing property " + desc.name); 224 properties.remove(desc); 225 } else { 226 Property<T> property = new Property<T>(desc, value); 227 log.debug("Setting property " + desc.name + " to value " + property.getValue()); 228 properties.put(desc, property); 229 } 230 } 231 232 /** 233 * Set a context property to a new value. If the provided value is null, then the property is removed. 234 * 235 * @param desc the property descriptor 236 * @param value the property value 237 * @param <T> the property parameter type 238 * @throws NullPointerException if the descriptor argument is null 239 * @throws IllegalArgumentException if the string value cannot be converted to the property type 240 */ 241 public <T> void setProperty(PropertyDescriptor<T> desc, String value) throws NullPointerException, IllegalArgumentException { 242 if (desc == null) { 243 throw new NullPointerException(); 244 } 245 if (value == null) { 246 log.debug("Removing property " + desc.name); 247 properties.remove(desc); 248 } else { 249 Property<T> property = desc.toProperty(value); 250 log.debug("Setting property " + desc.name + " to value " + property.getValue()); 251 properties.put(desc, property); 252 } 253 } 254 255 /** 256 * Load a resource from the context. 257 * 258 * @param resourceId the resource id 259 * @param resourceKind the resource kind 260 * @return the resource or null if it cannot be found 261 */ 262 public Resource loadResource(String resourceId, ResourceKind resourceKind) { 263 Resource res = null; 264 try { 265 266 // 267 switch (resourceKind) { 268 case LIFECYCLE: 269 if ("login".equals(resourceId) || "logout".equals(resourceId)) { 270 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 271 long timestamp = Long.MIN_VALUE; 272 for (File path : dirs) { 273 File f = path.child(resourceId + ".groovy", false); 274 if (f != null) { 275 Resource sub = f.getResource(); 276 if (sub != null) { 277 buffer.write(sub.getContent()); 278 buffer.write('\n'); 279 timestamp = Math.max(timestamp, sub.getTimestamp()); 280 } 281 } 282 } 283 return new Resource(buffer.toByteArray(), timestamp); 284 } 285 break; 286 case COMMAND: 287 // Find the resource first, we find for the first found 288 for (File path : dirs) { 289 File f = path.child(resourceId + ".groovy", false); 290 if (f != null) { 291 res = f.getResource(); 292 } 293 } 294 break; 295 case CONFIG: 296 String path = "/" + resourceId; 297 File file = confFS.get(Path.get(path)); 298 if (file != null) { 299 res = file.getResource(); 300 } 301 } 302 } catch (IOException e) { 303 log.warn("Could not obtain resource " + resourceId, e); 304 } 305 return res; 306 } 307 308 /** 309 * List the resources id for a specific resource kind. 310 * 311 * @param kind the resource kind 312 * @return the resource ids 313 */ 314 public List<String> listResourceId(ResourceKind kind) { 315 switch (kind) { 316 case COMMAND: 317 SortedSet<String> all = new TreeSet<String>(); 318 try { 319 for (File path : dirs) { 320 for (File file : path.children()) { 321 String name = file.getName(); 322 Matcher matcher = p.matcher(name); 323 if (matcher.matches()) { 324 all.add(matcher.group(1)); 325 } 326 } 327 } 328 } 329 catch (IOException e) { 330 e.printStackTrace(); 331 } 332 all.remove("login"); 333 all.remove("logout"); 334 return new ArrayList<String>(all); 335 default: 336 return Collections.emptyList(); 337 } 338 } 339 340 /** 341 * Returns the classloader associated with this context. 342 * 343 * @return the class loader 344 */ 345 public ClassLoader getLoader() { 346 return loader; 347 } 348 349 /** 350 * Returns the plugins associated with this context. 351 * 352 * @param pluginType the plugin type 353 * @param <T> the plugin generic type 354 * @return the plugins 355 */ 356 public <T> Iterable<T> getPlugins(Class<T> pluginType) { 357 return manager.getPlugins(pluginType); 358 } 359 360 /** 361 * Refresh the fs system view. This is normally triggered by the periodic job but it can be manually 362 * invoked to trigger explicit refreshes. 363 */ 364 public void refresh() { 365 try { 366 File commands = cmdFS.get(Path.get("/")); 367 List<File> newDirs = new ArrayList<File>(); 368 newDirs.add(commands); 369 for (File path : commands.children()) { 370 if (path.isDir()) { 371 newDirs.add(path); 372 } 373 } 374 dirs = newDirs; 375 } 376 catch (IOException e) { 377 e.printStackTrace(); 378 } 379 } 380 381 synchronized void start() { 382 if (!started) { 383 384 // Start refresh 385 Integer refreshRate = getProperty(PropertyDescriptor.VFS_REFRESH_PERIOD); 386 TimeUnit timeUnit = getProperty(PropertyDescriptor.VFS_REFRESH_UNIT); 387 if (refreshRate != null && refreshRate > 0) { 388 TimeUnit tu = timeUnit != null ? timeUnit : TimeUnit.SECONDS; 389 scanner = new ScheduledThreadPoolExecutor(1); 390 scanner.scheduleWithFixedDelay(new Runnable() { 391 public void run() { 392 refresh(); 393 } 394 }, 0, refreshRate, tu); 395 } 396 397 // Init plugins 398 manager.getPlugins(Object.class); 399 400 // 401 started = true; 402 } else { 403 log.warn("Attempt to double start"); 404 } 405 } 406 407 synchronized void stop() { 408 409 // 410 if (started) { 411 412 // Shutdown manager 413 manager.shutdown(); 414 415 // Shutdown scanner 416 if (scanner != null) { 417 ScheduledExecutorService tmp = scanner; 418 scanner = null; 419 tmp.shutdownNow(); 420 } 421 422 // Shutdown executor 423 executor.shutdownNow(); 424 } else { 425 log.warn("Attempt to stop when stopped"); 426 } 427 } 428 }