package org.atricore.idbus.bundles.datanucleus.core; import org.datanucleus.ClassLoaderResolver; import org.datanucleus.ObjectManagerFactoryImpl; import org.datanucleus.exceptions.NucleusException; import org.datanucleus.exceptions.NucleusUserException; import org.datanucleus.plugin.*; import org.datanucleus.util.Localiser; import org.datanucleus.util.NucleusLogger; import org.w3c.dom.*; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.net.URL; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.*; import java.util.jar.Manifest; /** * Parser for manifest.mf and plugin.xml files */ class PluginParser { protected static final Localiser LOCALISER = Localiser.getInstance("org.datanucleus.Localisation", ObjectManagerFactoryImpl.class.getClassLoader()); public static Bundle parseManifest(Manifest mf, URL fileUrl) { String symbolicName = getBundleSymbolicName(mf, null); String bundleVersion = getBundleVersion(mf, null); String bundleName = getBundleName(mf, null); String bundleVendor = getBundleVendor(mf, null); Bundle bundle = new Bundle(symbolicName, bundleName, bundleVendor, bundleVersion, fileUrl); bundle.setRequireBundle(getRequireBundle(mf)); return bundle; } /** * Accessor for the Bundle-Name from the manifest.mf file * @param mf the manifest * @return the Set with BundleDescription */ private static List getRequireBundle(Manifest mf) { String str = mf.getMainAttributes().getValue("Require-Bundle"); if( str == null || str.length() < 1 ) { return Collections.EMPTY_LIST; } Parser p = new Parser(str); List requiredBundle = new ArrayList(); String bundleSymbolicName = p.parseSymbolicName(); while(bundleSymbolicName != null) { Bundle.BundleDescription bd = new Bundle.BundleDescription(); bd.setBundleSymbolicName(bundleSymbolicName); bd.setParameters(p.parseParameters()); bundleSymbolicName = p.parseSymbolicName(); requiredBundle.add(bd); } return requiredBundle; } /** * Method to parse ExtensionPoints from plug-in file * @param rootElement the root element of the plugin xml * @param plugin the plugin bundle * @param clr the ClassLoaderResolver * @return a List of extensionPoints, if any * @throws org.datanucleus.exceptions.NucleusException if an error occurs during parsing */ private static List parseExtensionPoints(Element rootElement, Bundle plugin, ClassLoaderResolver clr) { List extensionPoints = new ArrayList(); try { NodeList elements = rootElement.getElementsByTagName("extension-point"); //System.out.println("Found extension-points " + elements.getLength()); for (int i = 0; i < elements.getLength(); i++) { Element element = (Element) elements.item(i); String id = element.getAttribute("id").trim(); String name = element.getAttribute("name"); String schema = element.getAttribute("schema"); //System.out.println("Extension id:" + id + ", name:" + name + ", schema:" + schema); extensionPoints.add(new ExtensionPoint(id, name, clr.getResource(schema, null), plugin)); } } catch (NucleusException ex) { throw ex; } return extensionPoints; } /** * Method to parse Extensions from plug-in file * @param rootElement the root element of the plugin xml * @param plugin the plugin bundle * @param clr the ClassLoaderResolver * @return a List of extensions, if any * @throws org.datanucleus.exceptions.NucleusException if an error occurs during parsing */ private static List parseExtensions(Element rootElement, Bundle plugin, ClassLoaderResolver clr) { List extensions = new ArrayList(); try { NodeList elements = rootElement.getElementsByTagName("extension"); for (int i = 0; i < elements.getLength(); i++) { Element element = (Element) elements.item(i); //System.out.println("Extension Element " + element + ", point=" + element.getAttribute("point")); Extension ex = new Extension(element.getAttribute("point"),plugin); NodeList elms = element.getChildNodes(); extensions.add(ex); for (int e = 0; e < elms.getLength(); e++) { if (elms.item(e) instanceof Element) { ex.addConfigurationElement(parseConfigurationElement(ex, (Element) elms.item(e), null)); } } } } catch (NucleusException ex) { throw ex; } return extensions; } /** * Accessor for the Bundle-SymbolicName from the manifest.mf file * @param mf the manifest * @param defaultValue a default value, in case no symbolic name found in manifest * @return the bundle symbolic name */ private static String getBundleSymbolicName(Manifest mf, String defaultValue) { if (mf == null) { return defaultValue; } String name = mf.getMainAttributes().getValue("Bundle-SymbolicName"); if (name == null) { return defaultValue; } StringTokenizer token = new StringTokenizer(name, ";"); return token.nextToken().trim(); } /** * Accessor for the Bundle-Name from the manifest.mf file * @param mf the manifest * @param defaultValue a default value, in case no name found in manifest * @return the bundle name */ private static String getBundleName(Manifest mf, String defaultValue) { if (mf == null) { return defaultValue; } String name = mf.getMainAttributes().getValue("Bundle-Name"); if (name == null) { return defaultValue; } return name; } /** * Accessor for the Bundle-Vendor from the manifest.mf file * @param mf the manifest * @param defaultValue a default value, in case no vendor found in manifest * @return the bundle vendor */ private static String getBundleVendor(Manifest mf, String defaultValue) { if (mf == null) { return defaultValue; } String vendor = mf.getMainAttributes().getValue("Bundle-Vendor"); if (vendor == null) { return defaultValue; } return vendor; } /** * Accessor for the Bundle-Version from the manifest.mf file * @param mf the manifest * @param defaultValue a default value, in case no version found in manifest * @return the bundle version */ private static String getBundleVersion(Manifest mf, String defaultValue) { if (mf == null) { return defaultValue; } String version = mf.getMainAttributes().getValue("Bundle-Version"); if (version == null) { return defaultValue; } return version; } /** * Method to parse Extensions in plug-in file. * @param db DocumentBuilder to use for parsing * @param mgr the PluginManager * @param fileUrl URL of the plugin.xml file * @param clr the ClassLoaderResolver * @return array of 2 elements. first element is a List of extensionPoints, and 2nd element is a List of Extension * @throws org.datanucleus.exceptions.NucleusException if an error occurs during parsing */ public static List[] parsePluginElements(DocumentBuilder db, PluginRegistry mgr, URL fileUrl, Bundle plugin, ClassLoaderResolver clr) { List extensionPoints = Collections.EMPTY_LIST; List extensions = Collections.EMPTY_LIST; InputStream is = null; try { is = fileUrl.openStream(); Element rootElement = db.parse(new InputSource(new InputStreamReader(is))).getDocumentElement(); //System.out.println("Root Element " + rootElement + " inputStream: " + is); if (NucleusLogger.PLUGIN.isDebugEnabled()) { NucleusLogger.PLUGIN.debug(LOCALISER.msg("024003", fileUrl.toString())); } extensionPoints = parseExtensionPoints(rootElement,plugin,clr); if (NucleusLogger.PLUGIN.isDebugEnabled()) { NucleusLogger.PLUGIN.debug(LOCALISER.msg("024004", fileUrl.toString())); } extensions = parseExtensions(rootElement,plugin,clr); } catch (NucleusException ex) { throw ex; } catch (Exception e) { NucleusLogger.PLUGIN.error(LOCALISER.msg("024000", fileUrl.getFile())); } finally { if (is != null) { try { is.close(); } catch (Exception e) { } } } return new List[]{extensionPoints, extensions}; } static DocumentBuilderFactory dbFactory = null; /** * Convenience method to create a document builder for parsing. * @return The document builder * @throws org.datanucleus.exceptions.NucleusException if an error occurs creating the instance */ public static DocumentBuilder getDocumentBuilder() { try { if (dbFactory == null) { dbFactory = DocumentBuilderFactory.newInstance(); } return dbFactory.newDocumentBuilder(); } catch (ParserConfigurationException e1) { throw new NucleusException(LOCALISER.msg("024016", e1.getMessage())); } } /** * Parses the current element and children, creating a ConfigurationElement object * @param ex the {@link org.datanucleus.plugin.Extension} * @param element the current element * @param parent the parent. null if the parent is Extension * @return the ConfigurationElement for the element */ private static ConfigurationElement parseConfigurationElement(Extension ex, Element element, ConfigurationElement parent) { ConfigurationElement confElm = new ConfigurationElement(ex, element.getNodeName(), parent); NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attribute = attributes.item(i); confElm.putAttribute(attribute.getNodeName(), attribute.getNodeValue()); } NodeList elements = element.getChildNodes(); for (int i = 0; i < elements.getLength(); i++) { if (elements.item(i) instanceof Element) { Element elm = (Element) elements.item(i); ConfigurationElement child = parseConfigurationElement(ex, elm, confElm); confElm.addConfigurationElement(child); } else if (elements.item(i) instanceof Text) { confElm.setText(elements.item(i).getNodeValue()); } } return confElm; } /** * Parse a Version Range as per OSGi spec 3.0 $3.2.5 * @param interval the interval string * @return */ public static Bundle.BundleVersionRange parseVersionRange(String interval) { Parser p = new Parser(interval); Bundle.BundleVersionRange versionRange = new Bundle.BundleVersionRange(); if( p.parseChar('[') ) { //inclusive versionRange.floor_inclusive = true; } else if( p.parseChar('(') ) { //exclusive versionRange.floor_inclusive = false; } versionRange.floor = new Bundle.BundleVersion(); versionRange.floor.major = p.parseIntegerLiteral().intValue(); if( p.parseChar('.') ) { versionRange.floor.minor = p.parseIntegerLiteral().intValue(); } if( p.parseChar('.') ) { versionRange.floor.micro = p.parseIntegerLiteral().intValue(); } if( p.parseChar('.') ) { versionRange.floor.qualifier = p.parseIdentifier(); } if( p.parseChar(',') ) { versionRange.ceiling = new Bundle.BundleVersion(); versionRange.ceiling.major = p.parseIntegerLiteral().intValue(); if( p.parseChar('.') ) { versionRange.ceiling.minor = p.parseIntegerLiteral().intValue(); } if( p.parseChar('.') ) { versionRange.ceiling.micro = p.parseIntegerLiteral().intValue(); } if( p.parseChar('.') ) { versionRange.ceiling.qualifier = p.parseIdentifier(); } if( p.parseChar(']') ) { //inclusive versionRange.ceiling_inclusive = true; } else if( p.parseChar(')') ) { //exclusive versionRange.ceiling_inclusive = false; } } return versionRange; } /** * Parser for a list of Bundle-Description * * @version $Revision: 1.15 $ **/ public static class Parser { private final String input; protected final CharacterIterator ci; /** * Constructor * @param input The input string **/ public Parser(String input) { this.input = input; ci = new StringCharacterIterator(input); } /** * Accessor for the input string. * @return The input string. */ public String getInput() { return input; } /** * Accessor for the current index in the input string. * @return The current index. */ public int getIndex() { return ci.getIndex(); } /** * Skip over any whitespace from the current position. * @return The new position */ public int skipWS() { int startIdx = ci.getIndex(); char c = ci.current(); while (Character.isWhitespace(c) || c == '\t' || c == '\f' || c == '\n' || c == '\r' || c == '\u0009' || c == '\u000c' || c == '\u0020' || c == '\11' || c == '\12' || c == '\14' || c == '\15' || c == '\40') { c = ci.next(); } return startIdx; } /** * Check if END OF TEXT is reach * @return true if END OF TEXT is reach */ public boolean parseEOS() { skipWS(); return ci.current() == CharacterIterator.DONE; } /** * Check if char <code>c</code> is found * @param c the Character to find * @return true if <code>c</code> is found */ public boolean parseChar(char c) { skipWS(); if (ci.current() == c) { ci.next(); return true; } else { return false; } } /** * Check if char <code>c</code> is found * @param c the Character to find * @param unlessFollowedBy the character to validate it does not follow <code>c</code> * @return true if <code>c</code> is found and not followed by <code>unlessFollowedBy</code> */ public boolean parseChar(char c, char unlessFollowedBy) { int savedIdx = skipWS(); if (ci.current() == c && ci.next() != unlessFollowedBy) { return true; } else { ci.setIndex(savedIdx); return false; } } /** * Parse an integer number from the current position. * @return The integer number parsed (null if not valid). */ public BigInteger parseIntegerLiteral() { int savedIdx = skipWS(); StringBuffer digits = new StringBuffer(); int radix; char c = ci.current(); if (c == '0') { c = ci.next(); if (c == 'x' || c == 'X') { radix = 16; c = ci.next(); while (isHexDigit(c)) { digits.append(c); c = ci.next(); } } else if (isOctDigit(c)) { radix = 8; do { digits.append(c); c = ci.next(); } while (isOctDigit(c)); } else { radix = 10; digits.append('0'); } } else { radix = 10; while (isDecDigit(c)) { digits.append(c); c = ci.next(); } } if (digits.length() == 0) { ci.setIndex(savedIdx); return null; } if (c == 'l' || c == 'L') { ci.next(); } return new BigInteger(digits.toString(), radix); } /** * Check if String <code>s</code> is found * @param s the String to find * @return true if <code>s</code> is found */ public boolean parseString(String s) { int savedIdx = skipWS(); int len = s.length(); char c = ci.current(); for (int i = 0; i < len; ++i) { if (c != s.charAt(i)) { ci.setIndex(savedIdx); return false; } c = ci.next(); } return true; } /** * Check if String <code>s</code> is found ignoring the case * @param s the String to find * @return true if <code>s</code> is found */ public boolean parseStringIgnoreCase(String s) { String lowerCasedString = s.toLowerCase(); int savedIdx = skipWS(); int len = lowerCasedString.length(); char c = ci.current(); for (int i = 0; i < len; ++i) { if (Character.toLowerCase(c) != lowerCasedString.charAt(i)) { ci.setIndex(savedIdx); return false; } c = ci.next(); } return true; } /** * Parse a java identifier from the current position. * @return The identifier */ public String parseIdentifier() { skipWS(); char c = ci.current(); if (!Character.isJavaIdentifierStart(c)) { return null; } StringBuffer id = new StringBuffer(); id.append(c); //- hifen symbol is valid according OSGi specification while (Character.isJavaIdentifierPart(c = ci.next()) || c=='-') { id.append(c); } return id.toString(); } /** * Parse an OSGi interval from the current position. * @return The interval */ public String parseInterval() { skipWS(); char c = ci.current(); StringBuffer id = new StringBuffer(); while ((c >='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9' ) || c=='.' || c=='_' || c=='-' || c=='[' || c==']' || c=='(' || c==')') { id.append(c); c = ci.next(); } return id.toString(); } /** * Parses the text string (up to the next space) and * returns it. The name includes '.' characters. * This can be used, for example, when parsing a class name wanting to * read in the full name (including package) so that it can then be * checked for existence in the CLASSPATH. * @return The name */ public String parseName() { int savedIdx = skipWS(); String id; if ((id = parseIdentifier()) == null) { return null; } StringBuffer qn = new StringBuffer(id); while (parseChar('.')) { if ((id = parseIdentifier()) == null) { ci.setIndex(savedIdx); return null; } qn.append('.').append(id); } return qn.toString(); } /** * Utility to return if a character is a decimal digit. * @param c The character * @return Whether it is a decimal digit */ private final boolean isDecDigit(char c) { return c >= '0' && c <= '9'; } /** * Utility to return if a character is a octal digit. * @param c The character * @return Whether it is a octal digit */ private final boolean isOctDigit(char c) { return c >= '0' && c <= '7'; } /** * Utility to return if a character is a hexadecimal digit. * @param c The character * @return Whether it is a hexadecimal digit */ private final boolean isHexDigit(char c) { return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'; } /** * Utility to return if the next non-whitespace character is a single quote. * @return Whether it is a single quote at the current point (ignoring whitespace) */ public boolean nextIsSingleQuote() { skipWS(); return (ci.current() == '\''); } /** * Utility to return if the next character is a dot. * @return Whether it is a dot at the current point */ public boolean nextIsDot() { return (ci.current() == '.'); } /** * Utility to return if the next character is a comma. * @return Whether it is a dot at the current point */ public boolean nextIsComma() { return (ci.current() == ','); } /** * Utility to return if the next character is a semi-colon. * @return Whether it is a semi-colon at the current point */ public boolean nextIsSemiColon() { return (ci.current() == ';'); } /** * Parse a String literal * @return the String parsed. null if single quotes or double quotes is found * @throws org.datanucleus.exceptions.NucleusUserException if an invalid character is found or the CharacterIterator is finished */ public String parseStringLiteral() { skipWS(); // Strings can be surrounded by single or double quotes char quote = ci.current(); if (quote != '"' && quote != '\'') { return null; } StringBuffer lit = new StringBuffer(); char c; while ((c = ci.next()) != quote) { if (c == CharacterIterator.DONE) { throw new NucleusUserException("Invalid string literal: " + input); } if (c == '\\') { c = parseEscapedCharacter(); } lit.append(c); } ci.next(); return lit.toString(); } /** * Parse a escaped character * @return the escaped char * @throws org.datanucleus.exceptions.NucleusUserException if a escaped character is not valid */ private char parseEscapedCharacter() { char c; if (isOctDigit(c = ci.next())) { int i = (c - '0'); if (isOctDigit(c = ci.next())) { i = i * 8 + (c - '0'); if (isOctDigit(c = ci.next())) { i = i * 8 + (c - '0'); } else { ci.previous(); } } else { ci.previous(); } if (i > 0xff) { throw new NucleusUserException("Invalid character escape: '\\" + Integer.toOctalString(i) + "'"); } return (char)i; } else { switch (c) { case 'b': return '\b'; case 't': return '\t'; case 'n': return '\n'; case 'f': return '\f'; case 'r': return '\r'; case '"': return '"'; case '\'': return '\''; case '\\': return '\\'; default: throw new NucleusUserException("Invalid character escape: '\\" + c + "'"); } } } public String remaining() { StringBuffer sb = new StringBuffer(); char c = ci.current(); while (c != CharacterIterator.DONE) { sb.append(c); c = ci.next(); } return sb.toString(); } public String toString() { return input; } public Map parseParameters() { skipWS(); Map paramaters = new HashMap(); while(nextIsSemiColon()) { parseChar(';'); skipWS(); String name = parseName(); skipWS(); if( !parseString(":=") && !parseString("=") ) { throw new NucleusUserException("Expected := or = symbols but found \""+remaining()+"\" at position "+this.getIndex()+" of text \""+input+"\""); } String argument = parseStringLiteral(); if( argument == null ) { argument = parseIdentifier(); } if( argument == null ) { argument = parseInterval(); } paramaters.put(name,argument); skipWS(); } return paramaters; } public String parseSymbolicName() { if( nextIsComma() ) { parseChar(','); } String name = parseName(); if( name == null && !parseEOS()) { throw new NucleusUserException("Invalid characters found \""+remaining()+"\" at position "+this.getIndex()+" of text \""+input+"\""); } return name; } } }