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 }