package org.xmlsh.sh.module; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.xmlsh.core.CoreException; import org.xmlsh.core.InvalidArgumentException; import org.xmlsh.core.ScriptCommand.SourceMode; import org.xmlsh.core.ScriptSource; import org.xmlsh.core.UnexpectedException; import org.xmlsh.core.XClassLoader; import org.xmlsh.sh.shell.Shell; import org.xmlsh.sh.shell.ShellConstants; import org.xmlsh.util.Assertions; import org.xmlsh.util.JavaUtils; import org.xmlsh.util.StringPair; import org.xmlsh.util.Util; public class ModuleFactory { private final static Logger mLogger = LogManager.getLogger(); public static Module createExternalModule( Shell shell , ModuleConfig config ) throws CoreException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { /* * If the module has a class try to use it * */ XClassLoader classLoader = shell.getClassLoader( config.getClassPath()); String modClassName = config.getModuleClass(); if( ! Util.isBlank(modClassName)){ mLogger.info("Loading module class: {}", modClassName); Class<?> modCls = JavaUtils.findClass(modClassName, classLoader); if( modCls != null ){ mLogger.info("Found class for external module: {}", modCls ); if( ! (Module.class.isAssignableFrom(modCls) ) ){ throw new InvalidArgumentException("Module class: is not an instance of Module: " + modCls); } Object mod = JavaUtils.newObject(modCls, config , classLoader); if( mod == null ) throw new InvalidArgumentException("Module could not be created: " +modClassName); if( !( mod instanceof Module)) throw new InvalidArgumentException("Module created is wrong class type: " +mod.getClass()); return mLogger.exit((Module)mod ); } } return mLogger.exit(new ExternalModule( config , classLoader)); } public static Module createJavaModule(Shell shell , ModuleConfig config ) throws CoreException { return new JavaModule( config, shell.getClassLoader( config.getClassPath())) ; } // static ModuleConfig createModuleModuleConfig(Shell shell , StringPair pair , List<URL> at ) throws Exception { if (pair.hasLeft()) { // prefix:name , prefix non-empty IModule m ; mLogger.trace("found prefix - trying moduel by prefix: ", pair); if( Util.isBlank(pair.getLeft()) ){ m = shell.getModule() ; mLogger.trace("blank prefix - use current module",m); } else { m = shell.getModuleByPrefix(pair.getLeft()); mLogger.debug("Preix module : " , m ); } // Allow C:/xxx/yyy to work // May look like a namespace but isnt if (m != null) { mLogger.trace("Found prefixed module - try getting child module" , m , pair.getRight()); ModuleConfig config = m.getModuleConfig(shell,pair.getRight(), at ); if (config != null) { mLogger.debug("Module Class found: " , config ); return config; } return mLogger.exit(null); } } /* * Try all default modules mLogger.debug("Try default modules"); for (IModule m : shell.getDefaultModules() ) { assert( m != null ); IModule sm = shell.getModule(); if( sm.equals(m)){ mLogger.trace("Skipping default module same as shell's module {}" , m ); continue; } if( RootModule.isEqual( m ) ) { mLogger.trace("Skipping root module {}",m); continue; } mLogger.trace("Trying module {} shell's module is {} " , m , sm ); Module mod = m.getModule(shell,pair.getRight()); if (mod != null) { return mLogger.exit(mod); } } */ return mLogger.exit(null); } /* public static IModule createModule(Shell shell, PName qname, List<URL> at ) throws Exception { mLogger.entry(shell, qname, at); ModuleConfig config = getModuleConfig(shell, qname, at); if( config == null ) return mLogger.exit(null); return mLogger.exit(createModule( shell , config )); } */ public static IModule createModule(Shell shell, ModuleConfig config) throws Exception { mLogger.entry(shell, config); assert( config != null ); IModule mod = null; switch( config.getType() ){ case "java" : mod = createJavaModule( shell, config ); break ; case "external" : mod = createExternalModule( shell , config ); break ; case "script" : mod = createScriptModule( shell , config ) ; break ; case "package" : mod = createPackageModule( shell , config ) ; break; default : assert(true); mLogger.error("Unexpected module configuration type: {} " , config.getType() ); break ; } assert( mod != null ); if( mod != null ) mod.onLoad(shell); return mLogger.exit(mod) ; } public static ModuleConfig getModuleConfig(Shell shell, PName qname, List<URL> at ) throws CoreException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, Exception, IOException, URISyntaxException { ModuleConfig config = null ; String name = qname.getName(); String prefix = qname.getPrefix(); // special scheme if(prefix != null && Util.isEqual(prefix, "java")) config = JavaModule.getConfiguration(shell, name, at) ; if( config == null && prefix == null ){ config = getInternalModuleConfig(shell , name , at ); } if( config == null ){ config = createModuleModuleConfig(shell, qname , at ); } if( config == null ) { // Try to find script source by usual means ScriptSource script = CommandFactory.getScriptSource(shell,qname ,SourceMode.IMPORT , at ); if( script != null ) config = ScriptModule.getConfiguration(shell ,script, at ); } if( config == null ) config = ExternalModule.getConfiguration(shell, qname.toString() , at); return config ; } /* * Look for a Class module in the internal packages * */ static ModuleConfig getInternalModuleConfig(Shell shell, String nameuri, List<URL> at ) throws CoreException, InvalidArgumentException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { ClassLoader classLoader = RootModule.getInstance().getClassLoader(); List<String> packages = Collections.singletonList("org.xmlsh.modules"); mLogger.debug("trying to find internal module by name: {} " , nameuri ); ModuleConfig config = getPackageModuleConfig( shell , nameuri , packages , at , RootModule.getInstance().getConfig().getHelpURI() ); return config; } public static ModuleConfig getInternalModuleConfig(Shell shell, String name, List<String> packages, String helpXml) { return new ModuleConfig("packages",name,null, null,null, null, shell.getSerializeOpts(), packages, helpXml); } // ModuleConfig config = new ModuleConfig( "package" , name, null , shell.getSerializeOpts() , pkgs, helpURL); /* * Create a PackageModule or derived Module based on configuration */ public static IModule createPackageModule( Shell shell, ModuleConfig config ) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, CoreException { mLogger.entry(config); assert( config != null ); Util.require( Assertions.isNotNull( config ) , "configuration required"); String moduleClassName = config.getModuleClass(); XClassLoader loader = shell.getClassLoader( config.getClassPath() ); if( ! Util.isBlank(moduleClassName)){ Class<?> cls = JavaUtils.findClass(moduleClassName, loader ); if( ! IModule.class.isAssignableFrom(cls) ) { mLogger.warn("Module class does not implement IModule" , cls ); mLogger.throwing(new UnexpectedException( "Module configuration specifies an invalid class type: " + cls.toString() ) ); } mLogger.info("Creating custom package module: {} " , cls); return mLogger.exit((IModule) JavaUtils.newObject(cls , config , loader )); } return mLogger.exit(new PackageModule( config , loader )); } public static Module createScriptModule(Shell shell, ModuleConfig config ) throws CoreException, IOException { return new ScriptModule( shell , config ); } /* * TEMPORARY HAck - try a name to see if it is an internal module in org.xmlsh.modules by looking * for a sub package */ public static ModuleConfig getPackageModuleConfig( Shell shell , String nameuri , List<String> packages , List<URL> at, String helpURI ) throws CoreException { mLogger.entry( nameuri); ModuleConfig config = new ModuleConfig("package"); for( String p : packages){ /* Look for module under modules */ String pkgn = p + "." + nameuri ; Package pkg = Package.getPackage( pkgn ); if( pkg == null ){ try { Class<?> cls = JavaUtils.findClass(pkgn + ".package-info" , shell.getClassLoader(at) ); mLogger.info("found package info clas: {} " + cls.getName() ); } catch( ClassNotFoundException e ){ // mLogger.catching(e); mLogger.trace("Cant find package-info - skipping "); } pkg = Package.getPackage( pkgn ); } if( pkg != null ){ mLogger.info("Found modules package: {} " , pkg.getName() ); config.setHelpURI(helpURI ); config.setName(pkg.getName()); config.setSerialOpts(shell.getSerializeOpts() ); config.setPackages(Collections.singletonList(pkg.getName())); reflectPackageAnnotations( pkg , config ); return mLogger.exit( config ); } } return mLogger.exit(null); } private static void reflectPackageAnnotations(Package pkg, ModuleConfig config) { org.xmlsh.annotations.Module ma = pkg.getAnnotation(org.xmlsh.annotations.Module.class); if( ma != null ){ if( !Util.isBlank(ma.name())) config.setName(ma.name()); String moduleClass = ma.moduleClass(); if( !Util.isBlank(moduleClass) ){ if( moduleClass.indexOf(ShellConstants.kDOT_CHAR) <0 ) moduleClass = pkg.getName() + "." + moduleClass ; config.setModuleClass(moduleClass); } if( ma.classes() != null ){ for( String clsname : ma.classes() ) config.addClassName( clsname ); } } } }