/*******************************************************************************
* Copyright (c) 2006-2013, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.core.version;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.eclipse.buckminster.core.KeyConstants;
import org.eclipse.buckminster.core.actor.IActionContext;
import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier;
import org.eclipse.buckminster.core.helpers.DateAndTimeUtils;
import org.eclipse.buckminster.core.metadata.MissingComponentException;
import org.eclipse.buckminster.core.metadata.WorkspaceInfo;
import org.eclipse.buckminster.core.reader.AbstractReaderType;
import org.eclipse.buckminster.core.reader.IReaderType;
import org.eclipse.buckminster.runtime.Buckminster;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
/**
* This class will generate qualifiers based on the last modification timestamp.
* The timestamp is obtained using the same @ IReaderType} that was used when
* the component was first materialized
*
* @author Thomas Hallgren
*/
public class TimestampQualifierGenerator extends AbstractQualifierGenerator {
public static final String FORMAT_PROPERTY = "generator.lastModified.format"; //$NON-NLS-1$
public static final String DEFAULT_FORMAT = "'v'yyyyMMddHHmm"; //$NON-NLS-1$
public static final String[] commonFormats = new String[] { DEFAULT_FORMAT, "'v'yyyyMMdd-HHmm", "'v'yyyyMMdd", //$NON-NLS-1$ //$NON-NLS-2$
"'I'yyyyMMddHHmm", "'I'yyyyMMdd-HHmm", "'I'yyyyMMdd" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
public static final DateFormat[] commonFormatters;
// Milliseconds corresponding to approximately 10 years
//
private static final long SANITY_THRESHOLD = (10L * 365L + 5L) * 24L * 60L * 60L * 1000L;
static {
int idx = commonFormats.length;
commonFormatters = new DateFormat[idx];
while (--idx >= 0) {
DateFormat dm = new SimpleDateFormat(commonFormats[idx]);
dm.setTimeZone(DateAndTimeUtils.UTC);
dm.setLenient(false);
commonFormatters[idx] = dm;
}
}
public static DateFormat getDateFormat(Map<String, ? extends Object> props) {
String format = (String) props.get(FORMAT_PROPERTY);
if (format == null)
format = DEFAULT_FORMAT;
DateFormat mf = new SimpleDateFormat(format);
mf.setTimeZone(DateAndTimeUtils.UTC);
mf.setLenient(false);
return mf;
}
private static Date getLastModification(ComponentIdentifier cid, IActionContext context) throws CoreException {
IPath location = WorkspaceInfo.getComponentLocation(cid);
IProject project = WorkspaceInfo.getProject(cid);
if (project == null) {
Buckminster.getLogger().debug("getLastModification: Failed determine project for component %s", cid); //$NON-NLS-1$
return null;
}
IReaderType readerType = AbstractReaderType.getTypeForResource(project);
if (readerType == null) {
Buckminster.getLogger().debug("getLastModification: Failed determine reader type for component %s", cid); //$NON-NLS-1$
return null;
}
try {
return readerType.getLastModification(location.toFile(), context.getCancellationMonitor());
} catch (RuntimeException e) {
throw BuckminsterException.wrap(e);
}
}
private static Date parseSaneDate(DateFormat mf, String str) throws ParseException {
long now = System.currentTimeMillis();
long sanePast = now - SANITY_THRESHOLD;
Date dt = mf.parse(str);
long tm = dt.getTime();
if (tm > now || tm < sanePast)
throw new ParseException("Bogus", 0); //$NON-NLS-1$
return dt;
}
@Override
public Version generateQualifier(IActionContext context, ComponentIdentifier cid, List<ComponentIdentifier> dependencies) throws CoreException {
final Version currentVersion = cid.getVersion();
if (currentVersion == null)
return null;
try {
Date lastMod = getLastModification(cid, context);
if (lastMod == null)
return currentVersion;
Map<String, ? extends Object> props = context.getProperties();
DateFormat mf = getDateFormat(props);
for (ComponentIdentifier dependency : dependencies) {
Version depVer = dependency.getVersion();
if (depVer == null)
continue;
String qualifier = VersionHelper.getQualifier(depVer);
if (qualifier == null)
continue;
Date depLastMod = null;
try {
depLastMod = parseSaneDate(mf, qualifier);
} catch (ParseException e) {
// Try the common formats. Use the first one that succeeds
//
synchronized (commonFormatters) {
for (int idx = 0; idx < commonFormatters.length; ++idx) {
try {
depLastMod = parseSaneDate(commonFormatters[idx], qualifier);
break;
} catch (ParseException e1) {
}
}
}
}
if (depLastMod == null) {
try {
// Replace the qualifier and attempt to find the real
// source in the workspace. If
// found, we use the SCM timestamp for that source.
//
depVer = VersionHelper.replaceQualifier(depVer, "qualifier"); //$NON-NLS-1$
depLastMod = getLastModification(new ComponentIdentifier(dependency.getName(), dependency.getComponentTypeID(), depVer),
context);
} catch (CoreException e) {
}
}
if (depLastMod != null && depLastMod.compareTo(lastMod) > 0)
lastMod = depLastMod;
}
String newQual = mf.format(lastMod);
newQual = VersionHelper.getQualifier(currentVersion).replace("qualifier", newQual); //$NON-NLS-1$
Version newVersion = VersionHelper.replaceQualifier(currentVersion, newQual);
IInstallableUnit prevIU = obtainFromReferenceRepo(cid, null);
if (prevIU == null)
return newVersion;
// Exactly the same version has been generated before
String buildId = prevIU.getProperty(KeyConstants.BUILD_ID);
if (buildId == null || buildId.equals(props.get("build.id"))) //$NON-NLS-1$
return newVersion;
// Component contains a generated build id that differs from the
// current one. We need todays date.
newQual = mf.format(BuildTimestampQualifierGenerator.getBuildTimestamp(context));
newQual = VersionHelper.getQualifier(currentVersion).replace("qualifier", newQual); //$NON-NLS-1$
return VersionHelper.replaceQualifier(currentVersion, newQual);
} catch (MissingComponentException e) {
return currentVersion;
}
}
}