Author: gnodet
Date: Thu Mar 27 09:49:46 2008
New Revision: 641885
URL: http://svn.apache.org/viewvc?rev=641885&view=rev
Log:
SMX4KNL-26: Spring URL to wrap xml spring files into bundles for direct
installation
Added:
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringTransformer.java
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringURLHandler.java
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/spring/
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/spring/extract.xsl
Modified:
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringDeploymentListener.java
servicemix/smx4/kernel/trunk/spring/src/main/resources/META-INF/spring/spring-deployer.xml
servicemix/smx4/kernel/trunk/spring/src/test/java/org/apache/servicemix/kernel/spring/SpringDeploymentListenerTest.java
Modified:
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringDeploymentListener.java
URL:
http://svn.apache.org/viewvc/servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringDeploymentListener.java?rev=641885&r1=641884&r2=641885&view=diff
==============================================================================
---
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringDeploymentListener.java
(original)
+++
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringDeploymentListener.java
Thu Mar 27 09:49:46 2008
@@ -69,89 +69,15 @@
}
public File handle(File artifact, File tmpDir) {
- InputStream is = null;
- OutputStream os = null;
try {
- Document doc = parse(artifact);
- String name = artifact.getName();
- String artifactId = name.substring(0, name.length() - 4);
- String version = "0.0.0";
- File destFile = new File(tmpDir, name + ".jar");
- Manifest m = new Manifest();
- m.getMainAttributes().putValue("Manifest-Version", "2");
- m.getMainAttributes().putValue("Bundle-SymbolicName", artifactId);
- m.getMainAttributes().putValue("Bundle-Version", version);
- m.getMainAttributes().putValue("Spring-Context",
"*;publish-context:=true;create-asynchronously:=true");
- String importPkgs = getImportPackages(doc);
- if (importPkgs != null && importPkgs.length() > 0) {
- m.getMainAttributes().putValue("Import-Package", importPkgs);
- }
-
- os = new FileOutputStream(destFile);
- JarOutputStream out = new JarOutputStream(os);
- ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
- out.putNextEntry(e);
- m.write(out);
- out.closeEntry();
- e = new ZipEntry("META-INF/");
- out.putNextEntry(e);
- e = new ZipEntry("META-INF/spring/");
- out.putNextEntry(e);
- out.closeEntry();
- e = new ZipEntry("META-INF/spring/" + artifact.getName());
- out.putNextEntry(e);
- is = new FileInputStream(artifact);
- copyInputStream(is, out);
- out.closeEntry();
- out.close();
+ File destFile = new File(tmpDir, artifact.getName() + ".jar");
+ FileOutputStream os = new FileOutputStream(destFile);
+ SpringTransformer.transform(artifact.toURL(), os);
+ os.close();
return destFile;
} catch (Exception e) {
LOGGER.info("Unable to build spring application bundle", e);
return null;
- } finally {
- try {
- is.close();
- } catch (Exception e) { }
- try {
- os.close();
- } catch (Exception e) { }
- }
- }
-
- protected String getImportPackages(Document doc) {
- Set<String> packages = getBeanPackages(doc);
- StringBuilder sb = new StringBuilder();
- for (String pkg : packages) {
- if (sb.length() > 0) {
- sb.append(",");
- }
- sb.append(pkg);
- }
- return sb.toString();
- }
-
- protected Set<String> getBeanPackages(Document doc) {
- Set<String> packages = new HashSet<String>();
- extractBeanPackages(doc, packages);
- return packages;
- }
-
- private void extractBeanPackages(Node node, Set<String> packages) {
- if (node instanceof Element) {
- Element element = (Element) node;
- String name = element.getLocalName();
- String uri = element.getNamespaceURI();
- if ("bean".equals(name) &&
"http://www.springframework.org/schema/beans".equals(uri)) {
- String clazz = element.getAttribute("class");
- if (clazz != null) {
- String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
- packages.add(pkg);
- }
- }
- }
- if (node != null) {
- extractBeanPackages(node.getFirstChild(), packages);
- extractBeanPackages(node.getNextSibling(), packages);
}
}
@@ -162,22 +88,6 @@
}
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(artifact);
- }
-
- /**
- * Copy in stream to an out stream
- *
- * @param in
- * @param out
- * @throws java.io.IOException
- */
- public static void copyInputStream(InputStream in, OutputStream out)
throws IOException {
- byte[] buffer = new byte[4096];
- int len = in.read(buffer);
- while (len >= 0) {
- out.write(buffer, 0, len);
- len = in.read(buffer);
- }
}
}
Added:
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringTransformer.java
URL:
http://svn.apache.org/viewvc/servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringTransformer.java?rev=641885&view=auto
==============================================================================
---
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringTransformer.java
(added)
+++
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringTransformer.java
Thu Mar 27 09:49:46 2008
@@ -0,0 +1,202 @@
+package org.apache.servicemix.kernel.spring;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Document;
+
+public class SpringTransformer {
+
+ static Transformer transformer;
+ static DocumentBuilderFactory dbf;
+
+
+ public static void transform(URL url, OutputStream os) throws Exception {
+ Document doc = parse(url);
+ String name = url.getPath();
+ int idx = name.lastIndexOf('/');
+ if (idx >= 0) {
+ name = name.substring(idx + 1);
+ }
+ String[] str = extractNameVersionType(name);
+
+ Manifest m = new Manifest();
+ m.getMainAttributes().putValue("Manifest-Version", "2");
+ m.getMainAttributes().putValue("Bundle-SymbolicName", str[0]);
+ m.getMainAttributes().putValue("Bundle-Version", str[1]);
+ m.getMainAttributes().putValue("Spring-Context",
"*;publish-context:=true;create-asynchronously:=true");
+ InputStream is = url.openStream();
+ String importPkgs = getImportPackages(analyze(is));
+ is.close();
+ if (importPkgs != null && importPkgs.length() > 0) {
+ m.getMainAttributes().putValue("Import-Package", importPkgs);
+ }
+
+ JarOutputStream out = new JarOutputStream(os);
+ ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
+ out.putNextEntry(e);
+ m.write(out);
+ out.closeEntry();
+ e = new ZipEntry("META-INF/");
+ out.putNextEntry(e);
+ e = new ZipEntry("META-INF/spring/");
+ out.putNextEntry(e);
+ out.closeEntry();
+ e = new ZipEntry("META-INF/spring/" + name);
+ out.putNextEntry(e);
+ is = url.openStream();
+ copyInputStream(is, out);
+ is.close();
+ out.closeEntry();
+ out.close();
+ }
+
+ private static final String DEFAULT_VERSION = "0.0.0";
+
+ private static final Pattern ARTIFACT_MATCHER =
Pattern.compile("(.+)(?:-(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?(?:[^a-zA-Z0-9](.*))?)(?:\\.([^\\.]+))",
Pattern.DOTALL);
+ private static final Pattern FUZZY_MODIFIDER =
Pattern.compile("(?:\\d+[.-])*(.*)", Pattern.DOTALL);
+
+ public static String[] extractNameVersionType(String url) {
+ Matcher m = ARTIFACT_MATCHER.matcher(url);
+ if (!m.matches()) {
+ return new String[] { url, DEFAULT_VERSION };
+ }
+ else {
+ //System.err.println(m.groupCount());
+ //for (int i = 1; i <= m.groupCount(); i++) {
+ // System.err.println("Group " + i + ": " + m.group(i));
+ //}
+
+ StringBuffer v = new StringBuffer();
+ String d1 = m.group(1);
+ String d2 = m.group(2);
+ String d3 = m.group(3);
+ String d4 = m.group(4);
+ String d5 = m.group(5);
+ String d6 = m.group(6);
+ if (d2 != null) {
+ v.append(d2);
+ if (d3 != null) {
+ v.append('.');
+ v.append(d3);
+ if (d4 != null) {
+ v.append('.');
+ v.append(d4);
+ if (d5 != null) {
+ v.append(".");
+ cleanupModifier(v, d5);
+ }
+ } else if (d5 != null) {
+ v.append(".0.");
+ cleanupModifier(v, d5);
+ }
+ } else if (d5 != null) {
+ v.append(".0.0.");
+ cleanupModifier(v, d5);
+ }
+ }
+ return new String[] { d1, v.toString(), d6 };
+ }
+ }
+
+ private static void cleanupModifier(StringBuffer result, String modifier) {
+ Matcher m = FUZZY_MODIFIDER.matcher(modifier);
+ if (m.matches()) {
+ modifier = m.group(1);
+ }
+ for (int i = 0; i < modifier.length(); i++) {
+ char c = modifier.charAt(i);
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A'
&& c <= 'Z') || c == '_' || c == '-') {
+ result.append(c);
+ }
+ }
+ }
+
+ public static Set<String> analyze(InputStream in) throws Exception {
+ if (transformer == null) {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Source source = new
StreamSource(SpringTransformer.class.getResourceAsStream("extract.xsl"));
+ transformer = tf.newTransformer(source);
+ }
+
+ Set<String> refers = new TreeSet<String>();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ Result r = new StreamResult(bout);
+ Source s = new StreamSource(in);
+ transformer.transform(s, r);
+
+ ByteArrayInputStream bin = new
ByteArrayInputStream(bout.toByteArray());
+ bout.close();
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(bin));
+
+ String line = br.readLine();
+ while (line != null) {
+ line = line.trim();
+ if (line.length() > 0) {
+ String parts[] = line.split("\\s*,\\s*");
+ for (int i = 0; i < parts.length; i++) {
+ int n = parts[i].lastIndexOf('.');
+ if (n > 0) {
+ refers.add(parts[i].substring(0, n));
+ }
+ }
+ }
+ line = br.readLine();
+ }
+ br.close();
+ return refers;
+ }
+
+ protected static String getImportPackages(Set<String> packages) {
+ StringBuilder sb = new StringBuilder();
+ for (String pkg : packages) {
+ if (sb.length() > 0) {
+ sb.append(",");
+ }
+ sb.append(pkg);
+ }
+ return sb.toString();
+ }
+
+ protected static Document parse(URL url) throws Exception {
+ if (dbf == null) {
+ dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+ }
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ return db.parse(url.toString());
+ }
+
+ protected static void copyInputStream(InputStream in, OutputStream out)
throws Exception {
+ byte[] buffer = new byte[4096];
+ int len = in.read(buffer);
+ while (len >= 0) {
+ out.write(buffer, 0, len);
+ len = in.read(buffer);
+ }
+ }
+}
Added:
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringURLHandler.java
URL:
http://svn.apache.org/viewvc/servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringURLHandler.java?rev=641885&view=auto
==============================================================================
---
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringURLHandler.java
(added)
+++
servicemix/smx4/kernel/trunk/spring/src/main/java/org/apache/servicemix/kernel/spring/SpringURLHandler.java
Thu Mar 27 09:49:46 2008
@@ -0,0 +1,98 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.servicemix.kernel.spring;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.servicemix.kernel.spring.SpringTransformer;
+import org.osgi.service.url.AbstractURLStreamHandlerService;
+
+/**
+ * A URL handler that will transform a JBI artifact to an OSGi bundle
+ * on the fly. Needs to be registered in the OSGi registry.
+ */
+public class SpringURLHandler extends AbstractURLStreamHandlerService {
+
+ private static Log logger = LogFactory.getLog(SpringURLHandler.class);
+
+ private static String SYNTAX = "spring: spring-xml-uri";
+
+ private URL springXmlURL;
+
+ /**
+ * Open the connection for the given URL.
+ *
+ * @param url the url from which to open a connection.
+ * @return a connection on the specified URL.
+ * @throws IOException if an error occurs or if the URL is malformed.
+ */
+ @Override
+ public URLConnection openConnection(URL url) throws IOException {
+ if (url.getPath() == null || url.getPath().trim().length() ==
0) {
+ throw new MalformedURLException ("Path can not be null
or empty. Syntax: " + SYNTAX );
+ }
+ springXmlURL = new URL(url.getPath());
+
+ logger.debug("Spring xml URL is: [" + springXmlURL + "]");
+ return new Connection(url);
+ }
+
+ public URL getSpringXmlURL() {
+ return springXmlURL;
+ }
+
+ public class Connection extends URLConnection {
+
+ public Connection(URL url) {
+ super(url);
+ }
+
+ @Override
+ public void connect() throws IOException {
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ try {
+ final File f = File.createTempFile("smx", "xml");
+ FileOutputStream os = new FileOutputStream(f);
+ SpringTransformer.transform(springXmlURL, os);
+ os.close();
+ return new FileInputStream(f) {
+ public void close() throws IOException {
+ super.close();
+ f.delete();
+ }
+ };
+ } catch (Exception e) {
+ logger.error("Error opening spring xml url", e);
+ throw (IOException) new IOException("Error opening spring xml
url").initCause(e);
+ }
+ }
+ }
+
+}
Modified:
servicemix/smx4/kernel/trunk/spring/src/main/resources/META-INF/spring/spring-deployer.xml
URL:
http://svn.apache.org/viewvc/servicemix/smx4/kernel/trunk/spring/src/main/resources/META-INF/spring/spring-deployer.xml?rev=641885&r1=641884&r2=641885&view=diff
==============================================================================
---
servicemix/smx4/kernel/trunk/spring/src/main/resources/META-INF/spring/spring-deployer.xml
(original)
+++
servicemix/smx4/kernel/trunk/spring/src/main/resources/META-INF/spring/spring-deployer.xml
Thu Mar 27 09:49:46 2008
@@ -39,4 +39,12 @@
</osgi:interfaces>
</osgi:service>
+ <bean id="springHandler"
class="org.apache.servicemix.kernel.spring.SpringURLHandler" />
+
+ <osgi:service ref="springHandler"
interface="org.osgi.service.url.URLStreamHandlerService">
+ <osgi:service-properties>
+ <entry key="url.handler.protocol" value="spring"/>
+ </osgi:service-properties>
+ </osgi:service>
+
</beans>
Added:
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/spring/extract.xsl
URL:
http://svn.apache.org/viewvc/servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/spring/extract.xsl?rev=641885&view=auto
==============================================================================
---
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/spring/extract.xsl
(added)
+++
servicemix/smx4/kernel/trunk/spring/src/main/resources/org/apache/servicemix/kernel/spring/extract.xsl
Thu Mar 27 09:49:46 2008
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:beans="http://www.springframework.org/schema/beans"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:jee="http://www.springframework.org/schema/jee"
+ xmlns:jms="http://www.springframework.org/schema/jms"
+ xmlns:lang="http://www.springframework.org/schema/lang"
+
xmlns:osgi-compendium="http://www.springframework.org/schema/osgi-compendium"
+ xmlns:osgi="http://www.springframework.org/schema/osgi"
+ xmlns:tool="http://www.springframework.org/schema/tool"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns:util="http://www.springframework.org/schema/util"
+
xmlns:webflow-config="http://www.springframework.org/schema/webflow-config">
+
+ <xsl:output method="text" />
+
+ <xsl:template match="/">
+
+ <!-- Match all attributes that holds a class or a comma
delimited
+ list of classes and print them -->
+
+ <xsl:for-each select="
+ //beans:bean/@class
+ | //beans:*/@value-type
+ | //aop:*/@implement-interface
+ | //aop:*/@default-impl
+ | //context:load-time-weaver/@weaver-class
+ | //jee:jndi-lookup/@expected-type
+ | //jee:jndi-lookup/@proxy-interface
+ | //jee:remote-slsb/@ejbType
+ | //jee:*/@business-interface
+ | //lang:*/@script-interfaces
+ | //osgi:*/@interface
+ | //util:list/@list-class
+ | //util:set/@set-class
+ | //util:map/@map-class
+ | //webflow-config:*/@class
+ ">
+ <xsl:value-of select="." />
+ <xsl:text>
+ </xsl:text>
+ </xsl:for-each>
+
+ <!-- This seems some magic to get extra imports? -->
+
+ <xsl:for-each select="//beans:[EMAIL
PROTECTED]'org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean'
+ or
@class='org.springframework.osgi.service.importer.support.OsgiServiceProxyFactoryBean']">
+ <xsl:for-each select="beans:[EMAIL
PROTECTED]'interfaces']">
+ <xsl:value-of select="@value" />
+ <xsl:text>
+ </xsl:text>
+ </xsl:for-each>
+ </xsl:for-each>
+
+ </xsl:template>
+
+
+</xsl:stylesheet>
+
Modified:
servicemix/smx4/kernel/trunk/spring/src/test/java/org/apache/servicemix/kernel/spring/SpringDeploymentListenerTest.java
URL:
http://svn.apache.org/viewvc/servicemix/smx4/kernel/trunk/spring/src/test/java/org/apache/servicemix/kernel/spring/SpringDeploymentListenerTest.java?rev=641885&r1=641884&r2=641885&view=diff
==============================================================================
---
servicemix/smx4/kernel/trunk/spring/src/test/java/org/apache/servicemix/kernel/spring/SpringDeploymentListenerTest.java
(original)
+++
servicemix/smx4/kernel/trunk/spring/src/test/java/org/apache/servicemix/kernel/spring/SpringDeploymentListenerTest.java
Thu Mar 27 09:49:46 2008
@@ -18,6 +18,9 @@
package org.apache.servicemix.kernel.spring;
import java.io.File;
+import java.io.FileInputStream;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.Set;
import junit.framework.TestCase;
@@ -27,9 +30,35 @@
public void testPackagesExtraction() throws Exception {
SpringDeploymentListener l = new SpringDeploymentListener();
File f = new
File(getClass().getClassLoader().getResource("META-INF/spring/spring-deployer.xml").toURI());
- Set<String> pkgs = l.getBeanPackages(l.parse(f));
+ Set<String> pkgs = SpringTransformer.analyze(new FileInputStream(f));
assertNotNull(pkgs);
- assertEquals(1, pkgs.size());
- assertEquals("org.apache.servicemix.kernel.spring",
pkgs.iterator().next());
+ assertEquals(2, pkgs.size());
+ Iterator<String> it = pkgs.iterator();
+ assertEquals("org.apache.servicemix.kernel.spring", it.next());
+ assertEquals("org.osgi.service.url", it.next());
}
+
+ public void testVersions() {
+
assertVersion("org.apache.servicemix.bundles.ant-1.7.0-1.0-m3-SNAPSHOT.jar",
+ "org.apache.servicemix.bundles.ant-1.7.0",
"1.0.0.m3-SNAPSHOT", "jar");
+ assertVersion("org.apache.activemq.core-1.0-SNAPSHOT.xml",
+ "org.apache.activemq.core", "1.0.0.SNAPSHOT", "xml");
+ assertVersion("org.apache.activemq.core-1.0.0-SNAPSHOT.xml",
+ "org.apache.activemq.core", "1.0.0.SNAPSHOT", "xml");
+ assertVersion("org.apache.activemq.core-1.0.0.xml",
+ "org.apache.activemq.core", "1.0.0", "xml");
+ assertVersion("geronimo-servlet_2.5_spec-1.1.2.jar",
+ "geronimo-servlet_2.5_spec", "1.1.2", "jar");
+ assertVersion("spring-aop-2.5.1.jar",
+ "spring-aop", "2.5.1", "jar");
+ }
+
+ private void assertVersion(String s, String... expectedParts) {
+ String[] parts = SpringTransformer.extractNameVersionType(s);
+ assertEquals(expectedParts.length, parts.length);
+ for (int i = 0; i < expectedParts.length; i++) {
+ assertEquals(expectedParts[i], parts[i]);
+ }
+ }
+
}