/*
* Copyright 2012 James Moger
*
* Licensed 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.moxie;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.TimeZone;
import org.moxie.utils.StringUtils;
public class Metadata {
public static final String snapshotTimestamp = "yyyyMMdd.HHmmss";
public static final String versionTimestamp = "yyyyMMddHHmmss";
public String groupId;
public String artifactId;
public String latest;
public String release;
public String version;
public Date lastUpdated;
private final List<String> versions;
private final List<Snapshot> snapshots;
public Metadata() {
lastUpdated = new Date(0);
versions = new ArrayList<String>();
snapshots = new ArrayList<Snapshot>();
}
public Metadata(Dependency dep, boolean isSnapshotMetadata) {
this();
// create metadata from dependency
groupId = dep.groupId;
artifactId = dep.artifactId;
if (isSnapshotMetadata && dep.isSnapshot()) {
// SNAPSHOT metadata
version = dep.version;
// revision is x.y.z-DATE.TIME-BUILDNUMBER
String [] values = dep.revision.split("-");
String timestamp = values[1];
String buildNumber = values[2];
addSnapshot(timestamp, buildNumber);
try {
lastUpdated = new SimpleDateFormat(snapshotTimestamp).parse(timestamp);
} catch (ParseException e) {
lastUpdated = new Date();
}
} else {
// ARTIFACT metadata
latest = dep.version;
versions.add(dep.version);
if (!dep.isSnapshot()) {
// RELEASE
release = dep.version;
}
lastUpdated = new Date();
}
}
public void addVersion(String version) {
versions.add(version);
}
public void addSnapshot(String timestamp, String buildNumber) {
snapshots.add(new Snapshot(timestamp, buildNumber));
}
public void merge(Metadata oldMetadata) {
// merge versions
LinkedHashSet<ArtifactVersion> vset = new LinkedHashSet<ArtifactVersion>();
for (String version : versions) {
vset.add(new ArtifactVersion(version));
}
for (String version : oldMetadata.versions) {
vset.add(new ArtifactVersion(version));
}
// sort them by Maven rules
List<ArtifactVersion> vlist = new ArrayList<ArtifactVersion>(vset);
Collections.sort(vlist);
// convert back to simple strings and determine LATEST and RELEASE
ArtifactVersion latest = null;
ArtifactVersion release = null;
versions.clear();
for (ArtifactVersion version : vlist) {
versions.add(version.toString());
if (StringUtils.isEmpty(version.getQualifier())) {
if (release == null || release.compareTo(version) == -1) {
release = version;
}
}
if (latest == null || latest.compareTo(version) == -1) {
latest = version;
}
}
if (release != null) {
this.release = release.toString();
}
if (latest != null) {
this.latest = latest.toString();
}
// merge snapshots
LinkedHashSet<Snapshot> sset = new LinkedHashSet<Snapshot>();
sset.addAll(snapshots);
sset.addAll(oldMetadata.snapshots);
List<Snapshot> slist = new ArrayList<Snapshot>(sset);
Collections.sort(slist);
snapshots.clear();
snapshots.addAll(slist);
if (oldMetadata.lastUpdated.after(lastUpdated)) {
lastUpdated = oldMetadata.lastUpdated;
}
}
public List<String> purgeSnapshots(PurgePolicy policy) {
List<String> removed = new ArrayList<String>();
if (snapshots.size() > policy.retentionCount) {
Collections.sort(snapshots);
// keep last RetentionCount snapshots
List<Snapshot> kept = new ArrayList<Snapshot>();
kept.addAll(snapshots.subList(snapshots.size() - policy.retentionCount, snapshots.size()));
if (policy.purgeAfterDays > 0) {
// determine which of the remaining snapshots should be purged
// or kept based on purgeAfterDays
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1 * policy.purgeAfterDays);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date threshold = cal.getTime();
SimpleDateFormat df = new SimpleDateFormat(snapshotTimestamp);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
List<Snapshot> candidates = snapshots.subList(0, snapshots.size() - policy.retentionCount);
for (Snapshot snapshot : candidates) {
try {
Date sDate = df.parse(snapshot.timestamp);
if (sDate.before(threshold)) {
removed.add(snapshot.getRevision());
} else {
kept.add(snapshot);
}
} catch (ParseException e) {
}
}
} else {
// just keep retentionCount revisions
for (Snapshot snapshot : snapshots.subList(0, snapshots.size() - policy.retentionCount)) {
removed.add(snapshot.getRevision());
}
}
Collections.sort(kept);
snapshots.clear();
snapshots.addAll(kept);
}
return removed;
}
public void setLastUpdated(String date) {
if (StringUtils.isEmpty(date) || "null".equalsIgnoreCase(date)) {
return;
}
try {
SimpleDateFormat df = new SimpleDateFormat(versionTimestamp);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
lastUpdated = df.parse(date);
} catch (ParseException e) {
// silently ignore malformed lastUpdate
}
}
public String getManagementId() {
return groupId + ":" + artifactId;
}
public String getSnapshotRevision() {
return snapshots.isEmpty() ? version : snapshots.get(snapshots.size() - 1).getRevision();
}
public int getLastBuildNumber() {
if (snapshots.isEmpty()) {
return 0;
}
int lastBuildNumber = 0;
for (Snapshot snapshot : snapshots) {
if (!StringUtils.isEmpty(snapshot.buildNumber)) {
try {
int bn = Integer.parseInt(snapshot.buildNumber);
if (bn > lastBuildNumber) {
lastBuildNumber = bn;
}
} catch (Throwable t) {
}
}
}
return lastBuildNumber;
}
public String resolveRangedVersion(String range) {
try {
VersionRange vr = VersionRange.createFromVersionSpec(range);
// resolve highest version possible from available versions
ArtifactVersion version = vr.matchVersion(getVersions());
return version.toString();
} catch (Exception e) {
throw new MoxieException(MessageFormat.format("Failed to resolve {0} because of unsupported version range: {1}", getManagementId(), range), e);
}
}
protected List<ArtifactVersion> getVersions() {
List<ArtifactVersion> list = new ArrayList<ArtifactVersion>();
for (String version : versions) {
list.add(new ArtifactVersion(version));
}
Collections.sort(list);
return list;
}
@Override
public String toString() {
return "maven-metadata.xml (" + getManagementId() + ")";
}
public String toXML() {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<metadata>\n");
// project metadata
sb.append(StringUtils.insertHalfTab("<!-- project metadata -->\n"));
sb.append(StringUtils.toXML("groupId", groupId));
sb.append(StringUtils.toXML("artifactId", artifactId));
sb.append(StringUtils.toXML("version", version));
// project versioning
sb.append(StringUtils.insertHalfTab("<!-- project versioning -->\n"));
sb.append(StringUtils.insertHalfTab("<versioning>\n"));
if (!StringUtils.isEmpty(latest) || !StringUtils.isEmpty(release)) {
StringBuilder node = new StringBuilder();
node.append(StringUtils.toXML("latest", latest));
node.append(StringUtils.toXML("release", release));
sb.append(StringUtils.insertHalfTab(node.toString()));
}
// snapshots
if (snapshots.size() > 0) {
sb.append(StringUtils.insertSoftTab("<!-- snapshots -->\n"));
for (Snapshot snapshot : snapshots) {
sb.append(StringUtils.insertSoftTab("<snapshot>\n"));
sb.append(StringUtils.insertSoftTab(StringUtils.toXML("timestamp", snapshot.timestamp)));
sb.append(StringUtils.insertSoftTab(StringUtils.toXML("buildNumber", snapshot.buildNumber)));
sb.append(StringUtils.insertSoftTab("</snapshot>\n"));
}
}
// versions
if (versions.size() > 0) {
sb.append(StringUtils.insertSoftTab("<!-- versions-->\n"));
sb.append(StringUtils.insertSoftTab("<versions>\n"));
StringBuilder sbv = new StringBuilder();
for (String version : versions) {
sbv.append(StringUtils.insertHalfTab(StringUtils.toXML("version", version)));
}
if (sbv.length() > 0) {
sb.append(StringUtils.insertHalfTab(sbv.toString()));
}
sb.append(StringUtils.insertSoftTab("</versions>\n"));
}
// set lastUpdated to now, if it is unset
if (lastUpdated.getTime() == 0) {
lastUpdated = new Date();
}
if (snapshots.size() > 0) {
// lastUpdated is most recent snapshot timestamp
SimpleDateFormat sf = new SimpleDateFormat(snapshotTimestamp);
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
lastUpdated = sf.parse(snapshots.get(snapshots.size() - 1).timestamp);
} catch (ParseException e) {
e.printStackTrace();
}
}
// set lastUpdated
SimpleDateFormat df = new SimpleDateFormat(versionTimestamp);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
sb.append(StringUtils.insertHalfTab(StringUtils.toXML("lastUpdated", df.format(lastUpdated))));
sb.append(StringUtils.insertHalfTab("</versioning>\n"));
// close metadata
sb.append("</metadata>");
return sb.toString();
}
private class Snapshot implements Comparable<Snapshot> {
final String timestamp;
final String buildNumber;
Snapshot(String timestamp, String buildNumber) {
this.timestamp = timestamp;
this.buildNumber = buildNumber;
}
public String getRevision() {
return version.replace("SNAPSHOT", timestamp + "-" + buildNumber);
}
@Override
public boolean equals(Object o) {
if (o instanceof Snapshot) {
return o.hashCode() == hashCode();
}
return false;
}
@Override
public int hashCode() {
return 11 + timestamp.hashCode() + buildNumber.hashCode();
}
@Override
public int compareTo(Snapshot o) {
return getRevision().compareTo(o.getRevision());
}
@Override
public String toString() {
return getRevision();
}
}
}