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