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