/* $Id$ */ package ibis.ipl; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; /** * This is the class responsible for starting an Ibis instance. During * initialization, this class determines which Ibis implementations are * available. It does so by finding all jar files in either the class path or * all jar files in the directories indicated by the ibis.ipl.impl.path * property. All Ibis implementations should be mentioned in the main properties * of the manifest of the jar file containing it, in the "Ibis-Starter" entry. * This entry should contain a comma- or space-separated list of class names, * where each class named provides an {@link IbisStarter} implementation. In * addition, a property "Ibis-IPL-Version" should be defined in the manifest, * containing a version number (e.g. 2.1). */ public final class IbisFactory { private static final String IPL_VERSION_STRING = "Ibis-IPL-Version"; private static final String STARTER_CLASS_STRING = "Ibis-Starter-Class"; private static final String IMPLEMENTATION_VERSION_STRING = "Ibis-Implementation-Version"; private static final String NICKNAME_STRING = "Ibis-NickName"; /** * Name of a backup manifest file, which is used for systems on which * a manifest file cannot be loaded (i.e. Android), */ public static final String IPL_MANIFEST_FILE = "ibis/ipl/IPL_MANIFEST"; /** * Nickname of the default Ibis implementation. This is the Ibis implementation * used, unless the user explicitly requests another implementation. */ public static final String DEFAULT_IMPLEMENTATION = "smartsockets"; /** Map of factories, one for each implementation path. */ private static final Map<String, IbisFactory> factories = new HashMap<String, IbisFactory>(); private static IbisFactory defaultFactory; private static final String VERSION = "2.2"; private static Properties manifestProperties = new Properties(); static { ClassLoader classLoader = IbisFactory.class.getClassLoader(); InputStream inputStream = classLoader .getResourceAsStream(IPL_MANIFEST_FILE); if (inputStream == null) { System.err.println( "IbisFactory Warning: could not load properties from manifest property file"); } else { try { manifestProperties.load(inputStream); } catch (IOException e) { System.err.println( "Warning: could not load properties from manifest property file"); } } } /** * This method is for internal use only. * It obtains a property from the IPL manifest file that is present in the * IPL jar. If the property is not present in the manifest file, <code>null</code> * is returned. * * @param p the property name. * @return the value of the specified property. */ public static String getManifestProperty(String p) { return manifestProperties.getProperty(p, null); } private static synchronized IbisFactory getFactory(String implPath, Properties properties) { if (implPath == null) { if (defaultFactory == null) { defaultFactory = new IbisFactory(null, properties); } return defaultFactory; } else { IbisFactory factory = factories.get(implPath); if (factory == null) { factory = new IbisFactory(implPath, properties); factories.put(implPath, factory); } return factory; } } /** * List of all available implementations. */ private Map<String, IbisStarter> implementations; private IbisFactory() { // DO NOT USE } /** * Constructs an Ibis factory, with the specified search path. * * @param implementationPath * the path to search for implementations. * @param properties * the properties to be used. */ private IbisFactory(String implementationPath, Properties properties) { implementations = new HashMap<String, IbisStarter>(); // load implementations from jar path loadIbisesFromJars(implementationPath); // load implementations from manifest property file loadIbisesFromManifestFile(); if (implementations.size() == 0) { throw new IbisConfigurationException( "Cannot find any Ibis implementations"); } } private static boolean isVerbose(Properties properties) { // see if the user specified "verbose" String verboseValue = properties.getProperty(IbisProperties.VERBOSE); return verboseValue != null && (verboseValue.equals("1") || verboseValue.equals("on") || verboseValue.equals("") || verboseValue.equals("true") || verboseValue .equals("yes")); } /** * Creates a new Ibis instance, making sure that the Ibis implementation * chosen implements the specified capabilities and port types. * * @param requiredCapabilities * ibis capabilities required by the application. * @param registryEventHandler * a {@link ibis.ipl.RegistryEventHandler RegistryEventHandler} * instance, or <code>null</code>. * @param portTypes * the list of port types required by the application. * @return the new Ibis instance. * * @exception IbisCreationFailedException * is thrown when the chosen Ibis implementation cannot be * created for some reason. */ public static Ibis createIbis(IbisCapabilities requiredCapabilities, RegistryEventHandler registryEventHandler, PortType... portTypes) throws IbisCreationFailedException { return createIbis(requiredCapabilities, null, true, registryEventHandler, portTypes); } /** * Creates a new Ibis instance, based on the specified properties, * making sure that the Ibis implementation * chosen implements the specified capabilities and port types. * * @param requiredCapabilities * ibis capabilities required by the application. * @param properties * properties that can be set, for instance a class path for * searching ibis implementations, or which registry to use. * There is a default, so <code>null</code> may be specified. * @param addDefaultConfigProperties * when set, the default properties are added, loaded from the system * properties, a "ibis.properties" file, etc, for as far as these * are not set in the <code>properties</code> parameter. * @param registryEventHandler * a {@link ibis.ipl.RegistryEventHandler RegistryEventHandler} * instance, or <code>null</code>. * @param portTypes * the list of port types required by the application. Can be an * empty list, but not null. * @return the new Ibis instance. * * @exception IbisCreationFailedException * is thrown when no Ibis was found that matches the * capabilities required, or a matching Ibis could not be * instantiated for some reason. */ public static Ibis createIbis(IbisCapabilities requiredCapabilities, Properties properties, boolean addDefaultConfigProperties, RegistryEventHandler registryEventHandler, PortType... portTypes) throws IbisCreationFailedException { return createIbis(requiredCapabilities, properties, addDefaultConfigProperties, registryEventHandler, null, (byte[]) null, portTypes); } /** * Creates a new Ibis instance, based on the specified properties, * making sure that the Ibis implementation * chosen implements the specified capabilities and port types. * * @param requiredCapabilities * ibis capabilities required by the application. * @param properties * properties that can be set, for instance a class path for * searching ibis implementations, or which registry to use. * There is a default, so <code>null</code> may be specified. * @param addDefaultConfigProperties * when set, the default properties are added, loaded from the system * properties, a "ibis.properties" file, etc, for as far as these * are not set in the <code>properties</code> parameter. * @param registryEventHandler * a {@link ibis.ipl.RegistryEventHandler RegistryEventHandler} * instance, or <code>null</code>. * @param credentials * Credentials used to join the pool. This could be a password, a * certificate, or something else. * @param portTypes * the list of port types required by the application. Can be an * empty list, but not null. * @return the new Ibis instance. * * @exception IbisCreationFailedException * is thrown when the chosen Ibis implementation cannot be * created for some reason. */ public static Ibis createIbis(IbisCapabilities requiredCapabilities, Properties properties, boolean addDefaultConfigProperties, RegistryEventHandler registryEventHandler, Credentials credentials, PortType... portTypes) throws IbisCreationFailedException { return createIbis(requiredCapabilities, properties, addDefaultConfigProperties, registryEventHandler, credentials, (byte[]) null, portTypes); } /** * Creates a new Ibis instance, based on the specified properties, * making sure that the Ibis implementation * chosen implements the specified capabilities and port types. * * @param requiredCapabilities * ibis capabilities required by the application. * @param properties * properties that can be set, for instance a class path for * searching ibis implementations, or which registry to use. * There is a default, so <code>null</code> may be specified. * @param addDefaultConfigProperties * when set, the default properties are added, loaded from the system * properties, a "ibis.properties" file, etc, for as far as these * are not set in the <code>properties</code> parameter. * @param registryEventHandler * a {@link ibis.ipl.RegistryEventHandler RegistryEventHandler} * instance, or <code>null</code>. * @param credentials * Credentials used to join the pool. This could be a password, a * certificate, or something else. * @param tag * A tag for this Ibis instance. * @param portTypes * the list of port types required by the application. Can be an * empty list, but not null. * @return the new Ibis instance. * * @exception IbisCreationFailedException * is thrown when the chosen Ibis implementation cannot be * created for some reason. */ public static Ibis createIbis(IbisCapabilities requiredCapabilities, Properties properties, boolean addDefaultConfigProperties, RegistryEventHandler registryEventHandler, Credentials credentials, String tag, PortType... portTypes) throws IbisCreationFailedException { byte[] tagBytes = null; if (tag != null) { try { tagBytes = tag.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new IbisCreationFailedException( "could not create tag from string", e); } } return createIbis(requiredCapabilities, properties, addDefaultConfigProperties, registryEventHandler, credentials, tagBytes, portTypes); } /** * Creates a new Ibis instance, based on the specified properties, * making sure that the Ibis implementation * chosen implements the specified capabilities and port types. * * @param requiredCapabilities * ibis capabilities required by the application. * @param properties * properties that can be set, for instance a class path for * searching ibis implementations, or which registry to use. * There is a default, so <code>null</code> may be specified. * @param addDefaultConfigProperties * when set, the default properties are added, loaded from the system * properties, a "ibis.properties" file, etc, for as far as these * are not set in the <code>properties</code> parameter. * @param registryEventHandler * a {@link ibis.ipl.RegistryEventHandler RegistryEventHandler} * instance, or <code>null</code>. * @param credentials * Credentials used to join the pool. This could be a password, a * certificate, or something else. * @param tag * A tag for this Ibis instance. * @param portTypes * the list of port types required by the application. Can be an * empty list, but not null. * @return the new Ibis instance. * @exception IbisCreationFailedException * is thrown when the chosen Ibis implementation cannot be * created for some reason. */ public static Ibis createIbis(IbisCapabilities requiredCapabilities, Properties properties, boolean addDefaultConfigProperties, RegistryEventHandler registryEventHandler, Credentials credentials, byte[] tag, PortType... portTypes) throws IbisCreationFailedException { Properties combinedProperties = new Properties(); // add default properties, if required if (addDefaultConfigProperties) { Properties defaults = IbisProperties.getDefaultProperties(); for (Enumeration<?> e = defaults.propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); String value = defaults.getProperty(key); combinedProperties.setProperty(key, value); } } // add user properties if (properties != null) { for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); String value = properties.getProperty(key); combinedProperties.setProperty(key, value); } } String implPath = combinedProperties .getProperty(IbisProperties.IMPLEMENTATION_PATH); // get/create factory IbisFactory factory = getFactory(implPath, combinedProperties); String specifiedImplementation = combinedProperties .getProperty(IbisProperties.IMPLEMENTATION); // create the ibis instance return factory.createIbis(registryEventHandler, requiredCapabilities, combinedProperties, credentials, tag, portTypes, specifiedImplementation); } /** * Factory does some initial sanity checks. Port types can only specify a * single connection capability, and must specify a serialization. */ private void checkSanity(RegistryEventHandler registryEventHandler, IbisCapabilities capabilities, PortType[] portTypes) throws IbisConfigurationException { for (PortType portType : portTypes) { // Check sanity of port types. int count = 0; if (portType.hasCapability(PortType.CONNECTION_MANY_TO_MANY)) { count++; } if (portType.hasCapability(PortType.CONNECTION_ONE_TO_ONE)) { count++; } if (portType.hasCapability(PortType.CONNECTION_ONE_TO_MANY)) { count++; } if (portType.hasCapability(PortType.CONNECTION_MANY_TO_ONE)) { count++; } if (count != 1) { throw new IbisConfigurationException("PortType " + portType + " should specify exactly one connection type"); } String[] strings = portType.getCapabilities(); boolean serializationSpecified = false; for (String s : strings) { if (s.startsWith(PortType.SERIALIZATION)) { serializationSpecified = true; break; } } if (!serializationSpecified) { throw new IbisConfigurationException("Port type " + portType + " should specify serialization"); } } // If a registryEventHandler is specified, the membership capability // must be requested as well. if (registryEventHandler != null && !capabilities .hasCapability(IbisCapabilities.MEMBERSHIP_UNRELIABLE) && !capabilities .hasCapability(IbisCapabilities.MEMBERSHIP_TOTALLY_ORDERED)) { throw new IbisConfigurationException( "RegistryEventHandler specified but no " + " membership capability requested"); } } /** * For internal use only. * Creates an ibis. Should only be used by Ibises to create "child" Ibises. * Applications should use the static functions to create an Ibis instance. * @param registryEventHandler * a {@link ibis.ipl.RegistryEventHandler RegistryEventHandler} * instance, or <code>null</code>. * @param requiredCapabilities * ibis capabilities required by the application. * @param properties * properties that can be set, for instance a class path for * searching ibis implementations, or which registry to use. * There is a default, so <code>null</code> may be specified. * @param credentials * Credentials used to join the pool. This could be a password, a * certificate, or something else. * @param applicationTag * A tag for this Ibis instance. * @param portTypes * the list of port types required by the application. Can be an * empty list, but not null. * @param specifiedImplementation * comma-separated implementation stack. * @return the new Ibis instance. * @exception IbisCreationFailedException * is thrown when the chosen Ibis implementation cannot be * created for some reason. */ public Ibis createIbis(RegistryEventHandler registryEventHandler, IbisCapabilities requiredCapabilities, Properties properties, Credentials credentials, byte[] applicationTag, PortType[] portTypes, String specifiedImplementation) throws IbisCreationFailedException { if (requiredCapabilities == null) { throw new IbisConfigurationException("capabilities not specified"); } if (portTypes == null) { throw new IbisConfigurationException("port types not specified"); } // print some info if (isVerbose(properties)) { System.err .println("IbisFactory: Looking for an IPL Implementation with capabilities: " + requiredCapabilities); System.err.println("(ibis) Properties:"); for (Enumeration<?> e = properties.propertyNames(); e .hasMoreElements();) { String key = (String) e.nextElement(); if (key.startsWith("ibis")) { String value = properties.getProperty(key); System.err.println(key + " = " + value); } } StringBuffer str = new StringBuffer(); str.append("IPL implementations:"); for (IbisStarter starter : implementations.values()) { str.append(" "); str.append(starter.getNickName()); } System.err.println(str.toString()); } checkSanity(registryEventHandler, requiredCapabilities, portTypes); // we allow users to specify implementations as impl1,impl2,impl3 to // denote a stack of implementations. We take the first part here, // and pass the rest to the ibis we create String specifiedSubImplementation = null; if (specifiedImplementation != null) { String[] parts = specifiedImplementation.split(",|;", 2); specifiedImplementation = parts[0]; if (parts.length == 2) { specifiedSubImplementation = parts[1]; } } IbisStarter starter = selectImplementation(requiredCapabilities, portTypes, specifiedImplementation); if (isVerbose(properties)) { System.err.println("IbisFactory: Selected ipl implementation: " + starter.getNickName()); } return starter.startIbis(this, registryEventHandler, properties, requiredCapabilities, credentials, applicationTag, portTypes, specifiedSubImplementation); } private IbisStarter selectImplementation( IbisCapabilities requiredCapabilities, PortType[] portTypes, String specifiedImplementation) throws IbisCreationFailedException { // The user specified an implementation. Try to find it, and see if it // matches the requirements if (specifiedImplementation != null) { IbisStarter starter = implementations.get(specifiedImplementation); if (starter == null) { throw new IbisCreationFailedException( "User specified implementation \"" + specifiedImplementation + "\" cannot be found"); } if (!starter.matches(requiredCapabilities, portTypes)) { CapabilitySet unmatchedCapabilities = starter .unmatchedIbisCapabilities(requiredCapabilities, portTypes); PortType[] remainingPortTypes = starter.unmatchedPortTypes( requiredCapabilities, portTypes); String portTypeString = ""; for (PortType portType : remainingPortTypes) { portTypeString = portTypeString + " " + portType; } throw new IbisCreationFailedException( "User specified implementation \"" + specifiedImplementation + "\" does not fulfill specified requirements. Unmatched capabilities = " + unmatchedCapabilities + ", Unmatched port-types = " + portTypeString); } return starter; } // auto detect implementation // find all matching implementations ArrayList<IbisStarter> matchingIbises = new ArrayList<IbisStarter>(); for (IbisStarter starter : implementations.values()) { if (starter.matches(requiredCapabilities, portTypes)) { matchingIbises.add(starter); } } // if no implementations match, throw an error if (matchingIbises.size() == 0) { throw new IbisCreationFailedException( "Cannot find Ibis Implementation matching requirements"); } // if only one implementation matches, use that one if (matchingIbises.size() == 1) { return matchingIbises.get(0); } // if multiple implementation match, return the default implementation for (IbisStarter starter : matchingIbises) { if (starter.getNickName().equals(DEFAULT_IMPLEMENTATION)) { return starter; } } // default not found in possible choices, we give up... String possibilities = ""; for (IbisStarter starter : matchingIbises) { possibilities = possibilities + " " + starter.getNickName(); } throw new IbisCreationFailedException( "Multiple ibis implementations matchs requirements, but the default implementation (\"" + DEFAULT_IMPLEMENTATION + "\") is not in list of possibilities: \"" + possibilities + "\", please select an ibis manually with the \"" + IbisProperties.IMPLEMENTATION + "\" property"); } /** * Creates a JarFile object from a given File object. */ private static JarFile getJarFile(File file) { try { if (!file.isFile() || !file.getName().endsWith(".jar")) { // not a jar file return null; } JarFile result = new JarFile(file, true); Manifest manifest = result.getManifest(); if (manifest != null) { manifest.getMainAttributes(); return result; } } catch (IOException e) { System.err.println("IbisFactory: Could not create jar file from file: " + file); } return null; } /** * This method reads all jar files found in the specified path, and stores * them in a list. */ private static JarFile[] readJarFiles(String path) { ArrayList<JarFile> result = new ArrayList<JarFile>(); StringTokenizer st = new StringTokenizer(path, File.pathSeparator); while (st.hasMoreTokens()) { String dir = st.nextToken(); File file = new File(dir); if (file.isFile()) { JarFile jarFile = getJarFile(file); if (jarFile != null) { result.add(jarFile); } } else if (file.isDirectory()) { File[] children = file.listFiles(); for (File child : children) { JarFile jarFile = getJarFile(child); if (jarFile != null) { result.add(jarFile); } } } else { System.err.println("IbisFactory: Not a file/directory: " + file); } } return result.toArray(new JarFile[0]); } private static IbisStarter loadIbisFromJar(JarFile jar, ClassLoader classLoader) { try { Manifest manifest = jar.getManifest(); Attributes attributes = manifest.getMainAttributes(); String iplVersion = attributes.getValue(IPL_VERSION_STRING); String implementationVersion = attributes .getValue(IMPLEMENTATION_VERSION_STRING); String nickName = attributes.getValue(NICKNAME_STRING); String starterClass = attributes.getValue(STARTER_CLASS_STRING); if (iplVersion == null || !iplVersion.startsWith(VERSION) || implementationVersion == null || nickName == null || starterClass == null) { return null; } return IbisStarter.createInstance(starterClass, classLoader, nickName, iplVersion, implementationVersion); } catch (Exception e) { System.err.println("IbisFactory: Could not load ibis from jar: " + jar.getName() + ": " + e); return null; } } private void loadIbisesFromJars(String implementationPath) { JarFile[] jarFiles; if (implementationPath == null) { implementationPath = System.getProperty("java.class.path"); } jarFiles = readJarFiles(implementationPath); // create ClassLoader for jar files URL[] urls = new URL[jarFiles.length]; for (int i = 0; i < jarFiles.length; i++) { try { File f = new File(jarFiles[i].getName()); urls[i] = f.toURI().toURL(); } catch (Exception e) { throw new Error(e); } } ClassLoader classLoader = new URLClassLoader(urls, this.getClass() .getClassLoader()); for (int i = 0; i < jarFiles.length; i++) { IbisStarter starter = loadIbisFromJar(jarFiles[i], classLoader); if (starter != null) { implementations.put(starter.getNickName(), starter); } } } private void loadIbisesFromManifestFile() { try { ClassLoader classLoader = getClass().getClassLoader(); String nickNames = manifestProperties.getProperty("implementations"); if (nickNames == null) { System.err .println("IbisFactory Warning: no implementations found in manifest property file"); } for (String nickName : nickNames.split(",")) { if (implementations.containsKey(nickName)) { // we already have this implementation, skip continue; } String iplVersion = manifestProperties.getProperty(nickName + ".ipl.version", null); String implementationVersion = manifestProperties.getProperty(nickName + ".version", null); String starterClass = manifestProperties.getProperty(nickName + ".starter.class", null); if (iplVersion == null || !iplVersion.startsWith(VERSION) || implementationVersion == null || nickName == null || starterClass == null) { continue; } IbisStarter starter = IbisStarter.createInstance(starterClass, classLoader, nickName, iplVersion, implementationVersion); if (starter != null) { implementations.put(nickName, starter); } } } catch (Throwable t) { System.err .println("IbisFactory Warning: could not load implementation from manifest property file: " + t); t.printStackTrace(System.err); } } }