/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.maven.internal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.buckminster.core.common.model.ExpandingProperties; import org.eclipse.buckminster.core.cspec.WellKnownExports; import org.eclipse.buckminster.core.cspec.builder.CSpecBuilder; import org.eclipse.buckminster.core.cspec.builder.ComponentRequestBuilder; import org.eclipse.buckminster.core.cspec.builder.GroupBuilder; import org.eclipse.buckminster.core.cspec.model.ComponentName; import org.eclipse.buckminster.core.cspec.model.PrerequisiteAlreadyDefinedException; import org.eclipse.buckminster.core.ctype.AbstractComponentType; import org.eclipse.buckminster.core.ctype.IResolutionBuilder; import org.eclipse.buckminster.core.helpers.TextUtils; import org.eclipse.buckminster.core.query.model.ComponentQuery; import org.eclipse.buckminster.core.reader.IComponentReader; import org.eclipse.buckminster.core.resolver.NodeQuery; import org.eclipse.buckminster.core.rmap.model.Provider; import org.eclipse.buckminster.core.version.MissingVersionTypeException; import org.eclipse.buckminster.core.version.VersionHelper; import org.eclipse.buckminster.core.version.VersionMatch; import org.eclipse.buckminster.core.version.VersionType; import org.eclipse.buckminster.maven.MavenPlugin; import org.eclipse.buckminster.maven.Messages; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.buckminster.runtime.Trivial; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.equinox.p2.metadata.IVersionFormat; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.metadata.VersionRange; import org.eclipse.osgi.util.NLS; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * This class is preliminary. A lot of things remain that concerns scope, * plugins, exclusion in transitive dependencies etc. * * @author Thomas Hallgren */ public class MavenComponentType extends AbstractComponentType { public static final String ID = "maven"; //$NON-NLS-1$ private static final MavenCSpecBuilder builder = new MavenCSpecBuilder(); private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.US); //$NON-NLS-1$ private static SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyyMMdd'.'HHmmss", Locale.US); //$NON-NLS-1$ private static final Pattern timestampPattern = Pattern.compile(// "^((?:19|20)\\d{2}(?:0[1-9]|1[012])(?:0[1-9]|[12][0-9]|3[01]))" + // //$NON-NLS-1$ "(?:\\.((?:[01][0-9]|2[0-3])[0-5][0-9][0-5][0-9]))?$"); //$NON-NLS-1$ public static Version createVersion(String versionStr) throws CoreException { versionStr = TextUtils.notEmptyTrimmedString(versionStr); if (versionStr == null) return null; Matcher m = timestampPattern.matcher(versionStr); if (m.matches()) return VersionHelper.getVersionType(VersionType.TIMESTAMP).getFormat().parse(versionStr); try { return getTripletFormat().parse(versionStr); } catch (IllegalArgumentException e) { return VersionHelper.getVersionType(VersionType.STRING).getFormat().parse(versionStr); } } static String addDependencies(IComponentReader reader, Document pomDoc, CSpecBuilder cspec, GroupBuilder archives, ExpandingProperties<String> properties) throws CoreException { Element project = pomDoc.getDocumentElement(); Node parentNode = null; Node propertiesNode = null; Node dependenciesNode = null; String groupId = null; String artifactId = null; String versionStr = null; String packaging = "jar"; //$NON-NLS-1$ for (Node child = project.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) continue; String nodeName = child.getNodeName(); if ("parent".equals(nodeName)) //$NON-NLS-1$ parentNode = child; else if ("properties".equals(nodeName)) //$NON-NLS-1$ propertiesNode = child; else if ("dependencies".equals(nodeName)) //$NON-NLS-1$ dependenciesNode = child; else if ("groupId".equals(nodeName)) //$NON-NLS-1$ groupId = child.getTextContent().trim(); else if ("artifactId".equals(nodeName)) //$NON-NLS-1$ artifactId = child.getTextContent().trim(); else if ("version".equals(nodeName)) //$NON-NLS-1$ versionStr = child.getTextContent().trim(); else if ("packaging".equals(nodeName)) //$NON-NLS-1$ packaging = child.getTextContent().trim(); } if (reader instanceof MavenReader && parentNode != null) processParentNode((MavenReader) reader, cspec, archives, properties, parentNode); if (groupId != null) { groupId = ExpandingProperties.expand(properties, groupId, 0); properties.put("project.groupId", groupId, true); //$NON-NLS-1$ properties.put("pom.groupId", groupId, true); //$NON-NLS-1$ properties.put("groupId", groupId, true); //$NON-NLS-1$ } if (artifactId != null) { artifactId = ExpandingProperties.expand(properties, artifactId, 0); properties.put("project.artifactId", artifactId, true); //$NON-NLS-1$ properties.put("pom.artifactId", artifactId, true); //$NON-NLS-1$ properties.put("artifactId", artifactId, true); //$NON-NLS-1$ } if (versionStr != null) { versionStr = ExpandingProperties.expand(properties, versionStr, 0); properties.put("project.version", versionStr, true); //$NON-NLS-1$ properties.put("pom.version", versionStr, true); //$NON-NLS-1$ properties.put("version", versionStr, true); //$NON-NLS-1$ } if (propertiesNode != null) processProperties(properties, propertiesNode); if (dependenciesNode != null) { NodeQuery nq = reader.getNodeQuery(); Provider provider = reader.getProviderMatch().getProvider(); ComponentQuery query = nq.getComponentQuery(); Map<String, ? extends Object> ctx = nq.getContext(); boolean transitive = (provider instanceof MavenProvider) ? ((MavenProvider) provider).isTransitive() : MavenProvider .getDefaultTransitive(); for (Node dep = dependenciesNode.getFirstChild(); dep != null; dep = dep.getNextSibling()) { if (dep.getNodeType() == Node.ELEMENT_NODE && "dependency".equals(dep.getNodeName()) && transitive) //$NON-NLS-1$ addDependency(query, ctx, provider, cspec, archives, properties, dep); } } return packaging; } static Date createTimestamp(String date, String time) throws CoreException { try { return (time != null) ? timestampFormat.parse(date + '.' + time) : dateFormat.parse(date); } catch (ParseException e) { throw BuckminsterException.wrap(e); } } static VersionMatch createVersionMatch(String versionStr, String typeInfo) throws CoreException { Version version = createVersion(versionStr); if (version == null) // // No version at all. Treat as if it was an unversioned SNAPSHOT // return VersionMatch.DEFAULT; return new VersionMatch(version, null, -1, null, typeInfo); } static VersionRange createVersionRange(String versionStr) throws CoreException { if (versionStr == null || versionStr.length() == 0) return null; char leadIn = versionStr.charAt(0); if (leadIn == '[' || leadIn == '(') { if (leadIn == '[' && versionStr.endsWith(",)")) //$NON-NLS-1$ { versionStr = versionStr.substring(1, versionStr.length() - 2); Version version = createVersion(versionStr); return (version == null) ? null : VersionHelper.greaterOrEqualRange(version); } return VersionHelper.createRange(getTripletFormat(), versionStr); } Version version = createVersion(versionStr); if (version == null) return null; return VersionHelper.exactRange(version); } static IVersionFormat getTripletFormat() { try { return VersionHelper.getVersionType(VersionType.TRIPLET).getFormat(); } catch (MissingVersionTypeException e) { throw new RuntimeException(e); } } static boolean isSnapshotVersion(Version version) { return version != null && version.toString().endsWith("SNAPSHOT"); //$NON-NLS-1$ } static Version stripFromSnapshot(Version version) { if (version == null) return null; String vstr = version.toString(); if (vstr.endsWith("SNAPSHOT")) //$NON-NLS-1$ { int stripLen = 8; if (vstr.charAt(vstr.length() - (stripLen + 1)) == '-') stripLen++; vstr = vstr.substring(0, vstr.length() - stripLen); } try { return version.getFormat().parse(vstr); } catch (IllegalArgumentException e) { return version; } } private static void addDependency(ComponentQuery query, Map<String, ? extends Object> context, Provider provider, CSpecBuilder cspec, GroupBuilder archives, ExpandingProperties<String> properties, Node dep) throws CoreException { String id = null; String groupId = null; String artifactId = null; String versionStr = null; String type = null; String scope = null; boolean optional = false; for (Node depChild = dep.getFirstChild(); depChild != null; depChild = depChild.getNextSibling()) { if (depChild.getNodeType() != Node.ELEMENT_NODE) continue; String localName = depChild.getNodeName(); String nodeValue = depChild.getTextContent().trim(); if ("groupId".equals(localName)) //$NON-NLS-1$ groupId = nodeValue; else if ("artifactId".equals(localName)) //$NON-NLS-1$ artifactId = nodeValue; else if ("version".equals(localName)) //$NON-NLS-1$ versionStr = nodeValue; else if ("id".equals(localName)) //$NON-NLS-1$ id = nodeValue; else if ("type".equals(localName)) //$NON-NLS-1$ type = nodeValue; else if ("optional".equals(localName)) //$NON-NLS-1$ optional = Boolean.parseBoolean(nodeValue); else if ("scope".equals(localName)) //$NON-NLS-1$ scope = nodeValue; } if (optional) // // Docs etc. We skip this here since we don't generate an // actions that can make use of it // return; boolean isScopeExcluded = (provider instanceof MavenProvider) ? ((MavenProvider) provider).isScopeExcluded(scope) : MavenProvider .getDefaultIsScopeExcluded(); if (isScopeExcluded) // // Determine if the scope of this POM is one we should ignore // return; if (artifactId == null) artifactId = id; if (artifactId == null) return; if ("plugin".equals(type)) //$NON-NLS-1$ // // Maven plugin (required for Maven builds). We don't want it. // return; if (groupId == null) groupId = artifactId; artifactId = ExpandingProperties.expand(properties, artifactId, 0); groupId = ExpandingProperties.expand(properties, groupId, 0); if (versionStr != null) versionStr = ExpandingProperties.expand(properties, versionStr, 0); String componentName = (provider instanceof MavenProvider) ? ((MavenProvider) provider).getComponentName(groupId, artifactId) : MavenProvider .getDefaultName(groupId, artifactId); if (componentName.contains("${")) //$NON-NLS-1$ { // Unresolved property. We can't use this so skip it. // MavenPlugin.getLogger().warning(NLS.bind(Messages.unable_to_resolve_component_name_0_skipping_dependency, componentName)); return; } ComponentName adviceKey = new ComponentName(componentName, ID); if (query.skipComponent(adviceKey, context)) return; ComponentRequestBuilder depBld = cspec.createDependencyBuilder(); depBld.setName(componentName); depBld.setComponentTypeID(ID); VersionRange vd = query.getVersionOverride(adviceKey, context); if (vd == null) vd = createVersionRange(versionStr); depBld.setVersionRange(vd); try { cspec.addDependency(depBld); archives.addExternalPrerequisite(depBld, WellKnownExports.JAVA_BINARIES); } catch (PrerequisiteAlreadyDefinedException e) { ComponentRequestBuilder oldDep = cspec.getRequiredDependency(depBld); if (!Trivial.equalsAllowNull(vd, oldDep.getVersionRange())) MavenPlugin.getLogger().warning(e.getMessage()); } } private static void processParentNode(MavenReader reader, CSpecBuilder cspec, GroupBuilder archives, ExpandingProperties<String> properties, Node parent) throws CoreException { String groupId = null; String artifactId = null; String versionStr = null; for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) continue; String localName = child.getNodeName(); String nodeValue = child.getTextContent().trim(); if ("groupId".equals(localName)) //$NON-NLS-1$ groupId = nodeValue; else if ("artifactId".equals(localName)) //$NON-NLS-1$ artifactId = nodeValue; else if ("version".equals(localName)) //$NON-NLS-1$ versionStr = nodeValue; } if (groupId != null) { groupId = ExpandingProperties.expand(properties, groupId, 0); properties.put("project.groupId", groupId, true); //$NON-NLS-1$ properties.put("pom.groupId", groupId, true); //$NON-NLS-1$ properties.put("groupId", groupId, true); //$NON-NLS-1$ } if (artifactId != null) { artifactId = ExpandingProperties.expand(properties, artifactId, 0); properties.put("project.artifactId", artifactId, true); //$NON-NLS-1$ properties.put("pom.artifactId", artifactId, true); //$NON-NLS-1$ properties.put("artifactId", artifactId, true); //$NON-NLS-1$ } Provider provider = reader.getProviderMatch().getProvider(); String componentName = (provider instanceof MavenProvider) ? ((MavenProvider) provider).getComponentName(groupId, artifactId) : MavenProvider .getDefaultName(groupId, artifactId); MapEntry entry = new MapEntry(componentName, groupId, artifactId, null); MavenReaderType mrt = (MavenReaderType) reader.getReaderType(); VersionMatch vm = mrt.createVersionMatch(reader, entry, versionStr); IPath parentPath = mrt.getPomPath(entry, vm); MavenPlugin.getLogger().debug("Getting POM information for parent: %s - %s at path %s", groupId, artifactId, parentPath); //$NON-NLS-1$ Document parentDoc = reader.getPOMDocument(entry, vm, parentPath, new NullProgressMonitor()); if (parentDoc == null) return; addDependencies(reader, parentDoc, cspec, archives, properties); } private static void processProperties(ExpandingProperties<String> properties, Node node) { for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) continue; String nodeName = child.getNodeName(); String nodeValue = child.getTextContent().trim(); if (nodeValue.length() > 0) properties.put(nodeName, ExpandingProperties.expand(properties, nodeValue, 0), true); else properties.remove(nodeName); } } @Override public IResolutionBuilder getResolutionBuilder(IComponentReader reader, IProgressMonitor monitor) throws CoreException { MonitorUtils.complete(monitor); return builder; } @Override public VersionRange getTypeSpecificDesignator(VersionRange range) { if (range == null) return null; Version low = range.getMinimum(); Version high = range.getMaximum(); boolean lowIsSnapshot = isSnapshotVersion(low); boolean highIsSnapshot = (high != null && isSnapshotVersion(high)); if (!(lowIsSnapshot || highIsSnapshot)) return range; low = stripFromSnapshot(low); if (high != null) high = stripFromSnapshot(high); if (!(low.isOSGiCompatible() && low.getSegmentCount() >= 3)) // // We cannot apply advanced semantics here so we just // strip the SNAPSHOT part and hope for the best. // return new VersionRange(low, range.getIncludeMinimum(), high, range.getIncludeMaximum()); StringBuilder bld = new StringBuilder(); bld.append(getTripletFormat()); bld.append('/'); if (lowIsSnapshot) { if (high == null || range.getIncludeMinimum()) { // [1.2.4-SNAPSHOT -> (1.2.3 // >=1.2.4-SNAPSHOT -> (1.2.3 // // Rationale: // In the triplet world the release 1.2.4 is higher then any // 1.2.4.SNAPSHOT // so we need something that is lower then 1.2.4 but higher then // 1.2.3. This // means "starting from 1.2.3 not including 1.2.3", i.e. (1.2.3. // We then want // to include everything up to the release of 1.2.4 // // The >= calls for a very high limit. We get to that later. // bld.append('('); int major = ((Integer) low.getSegment(0)).intValue(); int minor = ((Integer) low.getSegment(1)).intValue(); int micro = ((Integer) low.getSegment(2)).intValue(); if (minor > 0 || micro > 0) { bld.append(major); bld.append('.'); if (micro > 0) { bld.append(minor); bld.append('.'); bld.append(micro - 1); } else bld.append(minor - 1); } else bld.append(major - 1); } else { // (1.2.4.SNAPSHOT -> [1.2.4 // // Rationale: // 1.2.4.SNAPSHOT is lower then the 1.2.4 release. We let // (1.2.4.SNAPSHOT // mean "starting from 1.2.4.SNAPSHOT but not including the // SNAPSHOT which // in essence, is the same as starting from, and including, the // 1.2.4 release // bld.append('['); bld.append(low); } } else { // The best we can do here is to always include the low version. We // don't // have any semantics to apply // bld.append('['); bld.append(low); } bld.append(','); if (range.getMinimum().equals(range.getMaximum())) { bld.append(low); bld.append(']'); } else { if (high == null) { // Greater or equal. We need a ridiculously high version... // bld.append(Integer.MAX_VALUE); bld.append(']'); } else { if (range.getIncludeMaximum()) { // 1.2.3.SNAPSHOT] -> 1.2.3] // // The upper bound is included and a SNAPSHOT // can resolve to a release. // bld.append(high); bld.append(']'); } else { if (highIsSnapshot && high.isOSGiCompatible() && high.getSegmentCount() >= 3) { // ,1.2.3.SNAPSHOT) -> 1.2.2] // // We cannot use 1.2.3) here since that would // allow 1.2.3.xxx to be included since it's // lower. Instead, we include all up to the // 1.2.2 release // int major = ((Integer) high.getSegment(0)).intValue(); int minor = ((Integer) high.getSegment(1)).intValue(); int micro = ((Integer) high.getSegment(2)).intValue(); if (minor > 0 || micro > 0) { bld.append(major); bld.append('.'); if (micro > 0) { bld.append(minor); bld.append('.'); bld.append(micro - 1); } else bld.append(minor - 1); } else bld.append(major - 1); bld.append(']'); } else { bld.append(high); bld.append(')'); } } } } try { return new VersionRange(bld.toString()); } catch (IllegalArgumentException e) { return range; } } }