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    
020    package org.crsh.shell.impl.command;
021    
022    import groovy.lang.GroovyClassLoader;
023    import groovy.lang.GroovyCodeSource;
024    import groovy.lang.Script;
025    import org.codehaus.groovy.control.CompilationFailedException;
026    import org.codehaus.groovy.control.CompilerConfiguration;
027    import org.crsh.command.CommandInvoker;
028    import org.crsh.command.GroovyScriptCommand;
029    import org.crsh.command.NoSuchCommandException;
030    import org.crsh.plugin.PluginContext;
031    import org.crsh.plugin.ResourceKind;
032    import org.crsh.shell.ErrorType;
033    import org.crsh.util.TimestampedObject;
034    import org.crsh.vfs.Resource;
035    
036    import java.io.UnsupportedEncodingException;
037    import java.util.Map;
038    import java.util.concurrent.ConcurrentHashMap;
039    
040    class ClassManager<T> {
041    
042      /** . */
043      private final Map<String, TimestampedObject<Class<? extends T>>> classes = new ConcurrentHashMap<String, TimestampedObject<Class<? extends T>>>();
044    
045      /** . */
046      private final PluginContext context;
047    
048      /** . */
049      private final Class<? extends Script> baseScriptClass;
050    
051      /** . */
052      private final CompilerConfiguration config;
053    
054      /** . */
055      private final Class<T> baseClass;
056    
057      /** . */
058      private final ResourceKind kind;
059    
060      ClassManager(PluginContext context, ResourceKind kind, Class<T> baseClass, Class<? extends Script> baseScriptClass) {
061        CompilerConfiguration config = new CompilerConfiguration();
062        config.setRecompileGroovySource(true);
063        config.setScriptBaseClass(GroovyScriptCommand.class.getName());
064    
065        //
066        this.context = context;
067        this.baseScriptClass = baseScriptClass;
068        this.config = config;
069        this.baseClass = baseClass;
070        this.kind = kind;
071      }
072    
073      Class<? extends T> getClass(String name) throws NoSuchCommandException, NullPointerException {
074        if (name == null) {
075          throw new NullPointerException("No null argument allowed");
076        }
077    
078        TimestampedObject<Class<? extends T>> providerRef = classes.get(name);
079    
080        //
081        Resource script = context.loadResource(name, kind);
082    
083        //
084        if (script != null) {
085          if (providerRef != null) {
086            if (script.getTimestamp() != providerRef.getTimestamp()) {
087              providerRef = null;
088            }
089          }
090    
091          //
092          if (providerRef == null) {
093    
094            //
095            String source;
096            try {
097              source = new String(script.getContent(), "UTF-8");
098            }
099            catch (UnsupportedEncodingException e) {
100              throw new NoSuchCommandException(name, ErrorType.INTERNAL, "Could not compile command script " + name, e);
101            }
102    
103            //
104            Class<?> clazz;
105            try {
106              GroovyCodeSource gcs = new GroovyCodeSource(source, name, "/groovy/shell");
107              GroovyClassLoader gcl = new GroovyClassLoader(context.getLoader(), config);
108              clazz = gcl.parseClass(gcs, false);
109            }
110            catch (NoClassDefFoundError e) {
111              throw new NoSuchCommandException(name, ErrorType.INTERNAL, "Could not compile command script " + name, e);
112            }
113            catch (CompilationFailedException e) {
114              throw new NoSuchCommandException(name, ErrorType.INTERNAL, "Could not compile command script " + name, e);
115            }
116    
117            //
118            if (baseClass.isAssignableFrom(clazz)) {
119              Class<? extends T> providerClass = clazz.asSubclass(baseClass);
120              providerRef = new TimestampedObject<Class<? extends T>>(script.getTimestamp(), providerClass);
121              classes.put(name, providerRef);
122            } else {
123              throw new NoSuchCommandException(name, ErrorType.INTERNAL, "Parsed script " + clazz.getName() +
124                " does not implements " + CommandInvoker.class.getName());
125            }
126          }
127        }
128    
129        //
130        if (providerRef == null) {
131          return null;
132        }
133    
134        //
135        return providerRef.getObject();
136      }
137    
138      T getInstance(String name) throws NoSuchCommandException, NullPointerException {
139        Class<? extends T> clazz = getClass(name);
140        if (clazz == null) {
141          return null;
142        }
143    
144        //
145        try {
146          return clazz.newInstance();
147        }
148        catch (Exception e) {
149          throw new NoSuchCommandException(name, ErrorType.INTERNAL, "Could not create command " + name + " instance", e);
150        }
151      }
152    }