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 }