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