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 }