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