Author: norman.richards(a)jboss.com
Date: 2008-04-17 01:10:32 -0400 (Thu, 17 Apr 2008)
New Revision: 7962
Added:
trunk/src/main/org/jboss/seam/init/NamespacePackageResolver.java
trunk/src/test/integration/src/org/jboss/seam/test/integration/NamespaceResolverTest.java
Modified:
trunk/src/main/org/jboss/seam/init/Initialization.java
trunk/src/main/org/jboss/seam/init/NamespaceDescriptor.java
trunk/src/test/integration/src/org/jboss/seam/test/integration/testng.xml
Log:
JBSEAM-2775
Modified: trunk/src/main/org/jboss/seam/init/Initialization.java
===================================================================
--- trunk/src/main/org/jboss/seam/init/Initialization.java 2008-04-17 04:30:44 UTC (rev
7961)
+++ trunk/src/main/org/jboss/seam/init/Initialization.java 2008-04-17 05:10:32 UTC (rev
7962)
@@ -63,6 +63,7 @@
*/
public class Initialization
{
+ public static final String COMPONENT_NAMESPACE =
"http://jboss.com/products/seam/components";
public static final String COMPONENT_SUFFIX = ".component";
private static final LogProvider log = Logging.getLogProvider(Initialization.class);
@@ -73,6 +74,7 @@
private Set<Class> installedComponentClasses = new HashSet<Class>();
//private Set<String> importedPackages = new HashSet<String>();
private Map<String, NamespaceDescriptor> namespaceMap = new HashMap<String,
NamespaceDescriptor>();
+ private NamespacePackageResolver namespacePackageResolver = new
NamespacePackageResolver();
private Map<String, EventListenerDescriptor> eventListenerDescriptors = new
HashMap<String, EventListenerDescriptor>();
private Collection<String> globalImports = new ArrayList<String>();
@@ -173,117 +175,119 @@
}
}
+ private List<Element> elements(Element rootElement, String name) {
+ return rootElement.elements(name);
+ }
+
@SuppressWarnings("unchecked")
private void installComponentsFromXmlElements(Element rootElement, Properties
replacements)
- throws DocumentException, ClassNotFoundException
+ throws DocumentException, ClassNotFoundException
{
- /*List<Element> importJavaElements =
rootElement.elements("import-java-package");
- for (Element importElement : importJavaElements)
- {
- String pkgName = importElement.getTextTrim();
- importedPackages.add(pkgName);
- addNamespace( Package.getPackage(pkgName) );
- }*/
+ /*List<Element> importJavaElements =
rootElement.elements("import-java-package");
+ for (Element importElement : importJavaElements)
+ {
+ String pkgName = importElement.getTextTrim();
+ importedPackages.add(pkgName);
+ addNamespace( Package.getPackage(pkgName) );
+ }*/
- List<Element> importElements = rootElement.elements("import");
- for (Element importElement : importElements)
- {
- globalImports.add( importElement.getTextTrim() );
- }
-
- List<Element> componentElements =
rootElement.elements("component");
- for (Element component : componentElements)
- {
- installComponentFromXmlElement(component,
component.attributeValue("name"), component
- .attributeValue("class"), replacements);
- }
+ for (Element importElement: elements(rootElement,"import")) {
+ globalImports.add( importElement.getTextTrim() );
+ }
- List<Element> factoryElements = rootElement.elements("factory");
- for (Element factory : factoryElements)
- {
- installFactoryFromXmlElement(factory);
- }
+ for (Element component: elements(rootElement,"component")) {
+ installComponentFromXmlElement(component,
+ component.attributeValue("name"),
+ component.attributeValue("class"),
+ replacements);
+ }
- List<Element> elements = rootElement.elements("event");
- for (Element event: elements)
- {
- installEventListenerFromXmlElement(event);
- }
-
- for (Element elem : (List<Element>) rootElement.elements())
- {
- String ns = elem.getNamespace().getURI();
- NamespaceDescriptor nsInfo = namespaceMap.get(ns);
- if (nsInfo == null )
- {
- if ( ns!=null && ns.length()>0 &&
!ns.equals("http://jboss.com/products/seam/components") )
- {
- log.warn("namespace declared in components.xml does not resolve to a
package annotated @Namespace: " + ns);
- }
- }
- else
- {
- String name = elem.attributeValue("name");
- String elemName = toCamelCase( elem.getName(), true );
-
- String className = elem.attributeValue("class");
- if (className == null)
- {
- className = nsInfo.getPackage().getName() + '.' + elemName;
- }
-
- try
- {
- //get the class implied by the namespaced XML element name
- Class<Object> clazz = Reflections.classForName(className);
- Name nameAnnotation = clazz.getAnnotation(Name.class);
-
- //if the name attribute is not explicitly specified in the XML,
- //imply the name from the @Name annotation on the class implied
- //by the XML element name
- if (name == null && nameAnnotation!=null)
- {
- name = nameAnnotation.value();
- }
-
- //if this class already has the @Name annotation, the XML element
- //is just adding configuration to the existing component, don't
- //add another ComponentDescriptor (this is super-important to
- //allow overriding!)
- if ( nameAnnotation!=null && nameAnnotation.value().equals(name)
)
- {
- Install install = clazz.getAnnotation(Install.class);
- if ( install == null || install.value() )
- {
- className = null;
- }
- }
- }
- catch (ClassNotFoundException cnfe)
- {
- //there is no class implied by the XML element name so the
- //component must be defined some other way, assume that we are
- //just adding configuration, don't add a ComponentDescriptor
- //TODO: this is problematic, it results in elements getting
- // ignored when mis-spelled or given the wrong namespace!!
- className = null;
- }
+ for (Element factory: elements(rootElement,"factory")) {
+ installFactoryFromXmlElement(factory);
+ }
- //finally, if we could not get the name from the XML name attribute,
- //or from an @Name annotation on the class, imply it
- if (name == null)
- {
- String prefix = nsInfo.getNamespace().prefix();
- String componentName = toCamelCase(elem.getName(), false);
- name = Strings.isEmpty(prefix) ?
- componentName : prefix + '.' + componentName;
- }
+ for (Element event: elements(rootElement, "event")) {
+ installEventListenerFromXmlElement(event);
+ }
- installComponentFromXmlElement(elem, name, className, replacements);
- }
- }
+ for (Element elem : (List<Element>) rootElement.elements()) {
+ String ns = elem.getNamespace().getURI();
+ NamespaceDescriptor nsInfo = resolveNamespace(ns);
+ if (nsInfo == null && !ns.equals(COMPONENT_NAMESPACE)) {
+ log.warn("namespace declared in components.xml does not resolve to a package:
" + ns);
+ } else {
+ String name = elem.attributeValue("name");
+ String elemName = toCamelCase(elem.getName(), true);
+
+ String className = elem.attributeValue("class");
+ if (className == null) {
+ className = nsInfo.getPackageName() + '.' + elemName;
+ }
+
+ try {
+ //get the class implied by the namespaced XML element name
+ Class<Object> clazz = Reflections.classForName(className);
+ Name nameAnnotation = clazz.getAnnotation(Name.class);
+
+ //if the name attribute is not explicitly specified in the XML,
+ //imply the name from the @Name annotation on the class implied
+ //by the XML element name
+ if (name == null && nameAnnotation!=null) {
+ name = nameAnnotation.value();
+ }
+
+ //if this class already has the @Name annotation, the XML element
+ //is just adding configuration to the existing component, don't
+ //add another ComponentDescriptor (this is super-important to
+ //allow overriding!)
+ if (nameAnnotation!=null && nameAnnotation.value().equals(name)) {
+ Install install = clazz.getAnnotation(Install.class);
+ if (install == null || install.value()) {
+ className = null;
+ }
+ }
+ } catch (ClassNotFoundException cnfe) {
+ //there is no class implied by the XML element name so the
+ //component must be defined some other way, assume that we are
+ //just adding configuration, don't add a ComponentDescriptor
+ //TODO: this is problematic, it results in elements getting
+ // ignored when mis-spelled or given the wrong namespace!!
+ className = null;
+ }
+
+ //finally, if we could not get the name from the XML name attribute,
+ //or from an @Name annotation on the class, imply it
+ if (name == null) {
+ String prefix = nsInfo.getComponentPrefix();
+ String componentName = toCamelCase(elem.getName(), false);
+ name = Strings.isEmpty(prefix) ?
+ componentName : prefix + '.' + componentName;
+ }
+
+ installComponentFromXmlElement(elem, name, className, replacements);
+ }
+ }
}
+ private NamespaceDescriptor resolveNamespace(String namespace) {
+ if (Strings.isEmpty(namespace)) {
+ return null;
+ }
+
+ NamespaceDescriptor descriptor = namespaceMap.get(namespace);
+ if (descriptor == null) {
+ try {
+ String packageName = namespacePackageResolver.resolve(namespace);
+ descriptor = new NamespaceDescriptor(namespace, packageName);
+ namespaceMap.put(namespace, descriptor);
+ } catch (Exception e) {
+ log.warn("Could not determine java package for namespace: " + namespace,
e);
+ }
+ }
+
+ return descriptor;
+ }
+
@SuppressWarnings("unchecked")
private void installEventListenerFromXmlElement(Element event)
{
@@ -792,27 +796,26 @@
private void addNamespace(Package pkg)
{
- if (pkg != null)
- {
- Namespace ns = pkg.getAnnotation(Namespace.class);
- if (ns != null)
- {
- log.info("Namespace: " + ns.value() + ", package: " +
pkg.getName() + ", prefix: " + ns.prefix());
- NamespaceDescriptor old = namespaceMap.put(ns.value(), new
NamespaceDescriptor(ns, pkg));
- if ( old!=null && !old.getPackage().equals(pkg) )
- {
- throw new IllegalStateException("two packages with the same
@Namespace: " + ns.value());
- }
- }
- }
+ if (pkg != null) {
+ Namespace ns = pkg.getAnnotation(Namespace.class);
+ if (ns != null) {
+ log.info("Namespace: " + ns.value() + ", package: " +
pkg.getName() +
+ ", prefix: " + ns.prefix());
+
+ NamespaceDescriptor old = namespaceMap.put(ns.value(),
+ new NamespaceDescriptor(ns, pkg));
+ if (old!=null && !old.getPackageName().equals(pkg.getName())) {
+ throw new IllegalStateException("two packages with the same @Namespace:
" + ns.value());
+ }
+ }
+ }
}
private void addNamespaces()
{
- for ( Package pkg : standardDeploymentStrategy.getScannedNamespaces() )
- {
- addNamespace(pkg);
- }
+ for (Package pkg: standardDeploymentStrategy.getScannedNamespaces()) {
+ addNamespace(pkg);
+ }
}
private void initPropertiesFromServletContext()
Modified: trunk/src/main/org/jboss/seam/init/NamespaceDescriptor.java
===================================================================
--- trunk/src/main/org/jboss/seam/init/NamespaceDescriptor.java 2008-04-17 04:30:44 UTC
(rev 7961)
+++ trunk/src/main/org/jboss/seam/init/NamespaceDescriptor.java 2008-04-17 05:10:32 UTC
(rev 7962)
@@ -4,28 +4,38 @@
class NamespaceDescriptor
{
- private Namespace namespace;
- private Package pkg;
+ private String namespace;
+ private String packageName;
+ private String componentPrefix;
- NamespaceDescriptor(Namespace namespace, Package pkg)
- {
- this.namespace = namespace;
- this.pkg = pkg;
- }
+ NamespaceDescriptor(Namespace namespaceAnnotation, Package pkg)
+ {
+ this.namespace = namespaceAnnotation.value();
+ this.componentPrefix = namespaceAnnotation.prefix();
+ this.packageName = pkg.getName();
+ }
+
+ NamespaceDescriptor(String namespace, String packageName) {
+ this.namespace = namespace;
+ this.packageName = packageName;
+ this.componentPrefix = "";
+ }
- public Namespace getNamespace()
- {
- return namespace;
- }
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getComponentPrefix() {
+ return componentPrefix;
+ }
- public Package getPackage()
- {
- return pkg;
- }
+ public String getPackageName() {
+ return packageName;
+ }
- @Override
- public String toString()
- {
- return "EventListenerDescriptor(" + namespace + ')';
- }
+ @Override
+ public String toString()
+ {
+ return "NamespaceDescriptor(" + namespace + ')';
+ }
}
\ No newline at end of file
Added: trunk/src/main/org/jboss/seam/init/NamespacePackageResolver.java
===================================================================
--- trunk/src/main/org/jboss/seam/init/NamespacePackageResolver.java
(rev 0)
+++ trunk/src/main/org/jboss/seam/init/NamespacePackageResolver.java 2008-04-17 05:10:32
UTC (rev 7962)
@@ -0,0 +1,200 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Distributable under LGPL license. See terms of license at
gnu.org.
+ */
+package org.jboss.seam.init;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
+import org.testng.Assert;
+
+/**
+ * <p>Converts an <a
href="http://www.w3.org/TR/xml-names/">XML
namespace</a> to a Java package name.</p>
+ *
+ * <p>The conversion algorithm is as follows:
+ * <ul>
+ * <li>The XML namespace is parsed using <code><a
href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html"...
+ * java.net.URI</a></code>. Only absolute URIs are supported (i.e., a
scheme must be specified).</li>
+ * <li>URIs must be <i>hierarchical</i> (i.e., the scheme must be
followed by <code>//</code>) with one exception:
+ * <ul><li>If the scheme is <code>seam:</code>, the URI is
considered <i>opaque</i>, and is converted to a Java
+ * package using <a href="#seam_scheme">alternate
rules</a>.</li></ul></li>
+ * <li>The authority component must be <i>server-based</i> (nearly
all URI schemes currently in use are server-based).
+ * <li>The host portion of the namespace is converted as described by <a
+ *
href="http://java.sun.com/docs/books/jls/third_edition/html/packages...
7.7</a> of the
+ * <i>Java Language Specification, 3rd Edition</i></li>. That is,
"subdomains" are reversed from left-to-right to
+ * right-to-left order. The top-level domain becomes the root Java package.
</li>
+ * <li>The path, as returned by <code><a
href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html#getP...
+ * URI.getPath()</a></code> is mapped to further Java packages such that
each path element becomes another Java package
+ * appended in left-to-right order.</li>
+ * <li>A leading <code>www</code> subdomain, if specified, is
ignored.</li>
+ * <li>Values returned by <code><a
href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html#getU...
+ * URI.getUserInfo()</a></code>, <code><a
href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html#getP...
+ * URI.getPort()</a></code>, <code><a
href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html#getQ...
+ * URI.getQuery()</a></code>, and <code><a
href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html#getF...
+ * URI.getFragment()</a></code> are ignored.</li>
+ * </ul></p>
+ *
+ * <h2><a id="seam_proto" name="#seam_scheme">The
<code>seam:</code> Scheme</a></h2>
+ *
+ * <p>If the scheme is <code>seam:</code>, the URI is considered
<i>opaque</i>, and is converted to a Java package using
+ * these rules:
+ * <ul>
+ * <li>The <i>scheme-specific-part</i> is parsed into components
using the Java package delimiter, <code>period</code>
(".")</li>
+ * <li>Each component is appended, in left-to-right order, to build a complete
Java package.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>Characters specified in the URI which are not valid characters for Java
packages result in {@link #resolve(String)}
+ * throwing <code>IllegalArgumentException</code>, with one exception: the
hyphen ("-") character is converted to
+ * the valid Java package characer, the underscore ("_").</p>
+ *
+ * <h3>Valid Examples</h3>
+ *
+ * <ul>
+ * <li>seam:com.company.department.product ==>
com.company.department.product</li>
+ * <li>seam:org.acme-widgets.shipping.persistence ==>
org.acme_widgets.shipping.persistence</li>
+ * <
li>http://www.company.com/department/product ==>
com.company.department.product</li>
+ * <
li>https://my-company.com/department/product ==>
com.my_company.department.product</li>
+ *
<li>http://ericjung:password@www.company.com:8080/foo/bar/baz#anchor?param1=332¶m2=334
==> com.company.foo.bar.baz</li>
+ * </ul>
+ * <h3>Invalid Examples</h3>
+ * <ul>
+ * <
li>http://cats.import.com (<code>import</code> is a java
keyword)</li>
+ * <li>http://bar#foo#com</li>
+ * <li>seam:com!company!department</li>
+ * <li>com.company.department</li>
+ * <li>mailto:java-net@java.sun.com</li>
+ * <li>news:comp.lang.java</li>
+ * <li>urn:isbn:096139210x</li>
+ * </ul>
+ *
+ * @author <a href="mailto:eric DOT jung AT yahoo DOT com">Eric H.
Jung</a>
+ */
+public class NamespacePackageResolver {
+ private static final String JAVA_SCHEME = "java";
+
+ private static final LogProvider log =
+ Logging.getLogProvider(NamespacePackageResolver.class);
+
+
+ /**
+ * <p>Converts an XML namespace, <code>ns</code>, to a Stringified
package name according to the rules
+ * detailed in this class's javadoc.</p>
+ *
+ * <p>Characters specified in <code>ns</code> which are not valid
characters
+ * for Java packages result <code>IllegalArgumentException</code> being
thrown, with one exception. The
+ * hyphen ("-") character is converted to the valid Java package characer, the
underscore ("_").</p>
+ *
+ * @param ns the xml namespace to convert
+ *
+ * @returns a namespace descriptor
+ */
+ public String resolve(final String ns) {
+ try {
+ return parseURI(new URI(ns));
+ } catch (Exception e) {
+ // the exact exception doesn't matter here. The caller
+ // can log if needed
+ return null;
+ }
+ }
+
+ private String parseURI(URI uri) {
+ if (!uri.isAbsolute()) {
+ throw new IllegalArgumentException(uri + " is not an absolute URI");
+ }
+
+ return uri.isOpaque() ? parseOpaqueURI(uri) : parseHierarchicalURI(uri);
+ }
+
+
+ /**
+ * java:package
+ * seam:component
+ * seam:package:prefix
+ */
+ private String parseOpaqueURI(URI uri) {
+ if (uri.getScheme().equalsIgnoreCase(JAVA_SCHEME)) {
+ return uri.getSchemeSpecificPart();
+ }
+ throw new IllegalArgumentException("Unrecognized scheme in " + uri);
+ }
+
+ private String parseHierarchicalURI(URI uri) {
+ String scheme = uri.getScheme().toLowerCase();
+ if (!scheme.equals("http") && !scheme.equals("https")) {
+ throw new IllegalArgumentException("Hierarchical URLs must use http or https
scheme " + uri);
+ }
+
+ StringBuffer buf = new StringBuffer();
+
+ appendToPackageName(buf, hostnameToPackage(uri.getHost()));
+ appendToPackageName(buf, pathToPackage(uri.getPath()));
+
+ return buf.toString();
+ }
+
+ /**
+ * Convert path elements to package names in forward order
+ */
+ String pathToPackage(String path) {
+ StringBuffer buf = new StringBuffer();
+
+ if (path != null) {
+ String[] pathElements = path.split("/");
+ for (int i = 1, len = pathElements.length; i < len; i++) {
+ appendToPackageName(buf, pathElements[i]);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ String hostnameToPackage(String hostname) {
+ StringBuffer result = new StringBuffer();
+
+ String[] subdomains = hostname.split("\\.");
+
+ //Iterate through the subdomains in reverse converting each to a package name.
+ for (int i = subdomains.length - 1; i >= 0; i--) {
+ String subdomain = subdomains[i];
+ if (i > 0 || !subdomain.equalsIgnoreCase("www")) {
+ appendToPackageName(result, subdomain);
+ }
+ }
+
+ return result.toString();
+ }
+
+ private void appendToPackageName(StringBuffer buf, String subdomain) {
+ if (subdomain.length()>0) {
+ subdomain = makeSafeForJava(subdomain);
+
+ if (buf.length() > 0) {
+ buf.append('.');
+ }
+
+ buf.append(subdomain);
+ }
+ }
+
+ /**
+ * Converts characters in <code>subdomain</code> which aren't
java-friendly
+ * into java-friendly equivalents. Right now, we only support the conversion
+ * of hyphens ("-") to underscores ("_"). We could do other things
like toLowerCase(),
+ * but there are instances of upper-case package names in widespread use even by the
+ * likes of IBM (e.g., <a
href="http://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?t...
+ * COM.ibm.db2 classnames</a>).
+ *
+ * @param subdomain
+ * @return
+ */
+ private String makeSafeForJava(String subdomain) {
+ return subdomain.replace("-", "_");
+ }
+
+}
Added:
trunk/src/test/integration/src/org/jboss/seam/test/integration/NamespaceResolverTest.java
===================================================================
---
trunk/src/test/integration/src/org/jboss/seam/test/integration/NamespaceResolverTest.java
(rev 0)
+++
trunk/src/test/integration/src/org/jboss/seam/test/integration/NamespaceResolverTest.java 2008-04-17
05:10:32 UTC (rev 7962)
@@ -0,0 +1,51 @@
+package org.jboss.seam.test.integration;
+
+import org.jboss.seam.init.NamespacePackageResolver;
+import org.jboss.seam.mock.SeamTest;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class NamespaceResolverTest
+ extends SeamTest
+{
+ NamespacePackageResolver resolver = new NamespacePackageResolver();
+
+ @Test
+ public void testResolver() {
+
+ test("java:foo", "foo");
+ test("java:com.company.department",
+ "com.company.department");
+ test("java:com.company.department.product",
+ "com.company.department.product");
+
test("http://www.company.com/department/product",
+ "com.company.department.product");
+
test("https://my-company.com/department/product",
+ "com.my_company.department.product");
+ test("http://ericjung:password@www.company.com:8080/foo/bar/baz#anchor?param1=332¶m2=334",
+ "com.company.foo.bar.baz");
+ test("http://cats.import.com",
+ "com.import.cats");
+
+
+ //testFail("http://bar#foo#com");
+
+ testFail("java:");
+
+ // need to think about this one
+ //testFail("java:foo!bar");
+
+ testFail("mailto:java-net@java.sun.com");
+ testFail("news:comp.lang.java");
+ testFail("urn:isbn:096139210x");
+
+ }
+
+ private void test(String namespace, String packageName) {
+ Assert.assertEquals(resolver.resolve(namespace), packageName);
+ }
+
+ private void testFail(String namespace) {
+ Assert.assertNull(resolver.resolve(namespace), namespace);
+ }
+}
Modified: trunk/src/test/integration/src/org/jboss/seam/test/integration/testng.xml
===================================================================
--- trunk/src/test/integration/src/org/jboss/seam/test/integration/testng.xml 2008-04-17
04:30:44 UTC (rev 7961)
+++ trunk/src/test/integration/src/org/jboss/seam/test/integration/testng.xml 2008-04-17
05:10:32 UTC (rev 7962)
@@ -8,9 +8,9 @@
<class name="org.jboss.seam.test.integration.ImportTest" />
<class name="org.jboss.seam.test.integration.NamespaceTest"
/>
<class
name="org.jboss.seam.test.integration.JavaBeanEqualsTest"/>
+ <class
name="org.jboss.seam.test.integration.NamespaceResolverTest" />
</classes>
</test>
-
<test name="Seam Integration Tests: Persistence">
<classes>
<class name="org.jboss.seam.test.integration.EntityTest"/>