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