/*****************************************************************************
* 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.materializer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.buckminster.core.CorePlugin;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier;
import org.eclipse.buckminster.core.helpers.FileUtils;
import org.eclipse.buckminster.core.helpers.FileUtils.DeleteException;
import org.eclipse.buckminster.core.metadata.StorageManager;
import org.eclipse.buckminster.core.metadata.WorkspaceInfo;
import org.eclipse.buckminster.core.metadata.model.Materialization;
import org.eclipse.buckminster.core.metadata.model.Resolution;
import org.eclipse.buckminster.core.mspec.ConflictResolution;
import org.eclipse.buckminster.core.mspec.IMaterializationNode;
import org.eclipse.buckminster.core.mspec.model.MaterializationSpec;
import org.eclipse.buckminster.core.reader.IComponentReader;
import org.eclipse.buckminster.core.reader.IReaderType;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.IOUtils;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.osgi.util.NLS;
/**
* Materializes each component to the local filesystem.
*
* @author Thomas Hallgren
*/
public class FileSystemMaterializer extends AbstractMaterializer {
@Override
public String getMaterializerRootDir() {
return "downloads"; //$NON-NLS-1$
}
@Override
public List<Materialization> materialize(List<Resolution> resolutions, MaterializationContext context, IProgressMonitor monitor)
throws CoreException {
ArrayList<Materialization> adjustedMinfos = new ArrayList<Materialization>(resolutions.size());
HashMap<ComponentIdentifier, Resolution> resolutionPerID = new HashMap<ComponentIdentifier, Resolution>();
Logger logger = CorePlugin.getLogger();
StorageManager sm = StorageManager.getDefault();
Set<ComponentIdentifier> updateCandidates = new HashSet<ComponentIdentifier>();
MaterializationStatistics statistics = context.getMaterializationStatistics();
monitor.beginTask(null, 1000);
try {
// Group materialization infos per reader. Some readers use a common
// "view"
// for all materializations. They need to be initialized using all
// entries.
//
Map<String, List<Materialization>> perReader = new TreeMap<String, List<Materialization>>();
MaterializationSpec mspec = context.getMaterializationSpec();
// Obtain some locations where we can feel it is OK to remove a
// subfolder.
//
IPath filesRoot = mspec.getInstallLocation();
IPath workspaceRoot = mspec.getWorkspaceLocation();
if (workspaceRoot == null)
workspaceRoot = ResourcesPlugin.getWorkspace().getRoot().getLocation();
IPath userTemp = Path.fromOSString(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
IPath userHome = Path.fromOSString(System.getProperty("user.home")); //$NON-NLS-1$
IProgressMonitor prepMon = MonitorUtils.subMonitor(monitor, 100);
prepMon.beginTask(null, resolutions.size() * 10);
for (Resolution cr : resolutions) {
ComponentIdentifier ci = null;
try {
cr.store(sm);
ci = cr.getComponentIdentifier();
ConflictResolution conflictRes = mspec.getConflictResolution(cr);
IPath artifactLocation = getArtifactLocation(context, cr);
String syncLock = artifactLocation.toOSString().intern();
synchronized (syncLock) {
Materialization mat = WorkspaceInfo.getMaterialization(cr);
if (mat != null) {
if (mat.getComponentLocation().equals(artifactLocation)) {
if (conflictRes == ConflictResolution.KEEP) {
// The same component (name, version, and
// type) is already materialized to
// the same location.
//
statistics.addKept(ci);
adjustedMinfos.add(mat);
continue;
}
}
mat.remove(sm);
}
mat = new Materialization(artifactLocation, ci);
resolutionPerID.put(ci, cr);
File file = artifactLocation.toFile().getAbsoluteFile();
boolean fileExists = file.exists();
if (fileExists && conflictRes == ConflictResolution.KEEP) {
boolean pathTypeOK = artifactLocation.hasTrailingSeparator() ? file.isDirectory() : !file.isDirectory();
if (!pathTypeOK)
throw new FileFolderMismatchException(ci, artifactLocation);
// Don't materialize this one. Instead, pretend that
// we
// just did.
//
statistics.addKept(ci);
logger.info(NLS.bind(Messages.Skipping_materialization_of_0_Instead_reusing_1, ci, artifactLocation));
mat.store(sm);
adjustedMinfos.add(mat);
MonitorUtils.worked(prepMon, 10);
continue;
}
// Ensure that the destination exists and that it is
// empty. This might cause a
// DestinationNotEmpty exception to be thrown.
//
if (artifactLocation.hasTrailingSeparator()) {
// We are installing into folder
//
IMaterializationNode node = mspec.getMatchingNode(cr);
if (node != null && node.isUnpack()) {
// An unpack must never clear the folder that it
// uses as parent for the unpack
// unless that parent has been explicitly
// stated.
//
if (node.getLeafArtifact() == null)
conflictRes = ConflictResolution.UPDATE;
}
if (conflictRes.equals(ConflictResolution.REPLACE)) {
// Some precaution is needed here. We don't just
// remove folders.
//
int alCount = artifactLocation.segmentCount();
if (!((userHome.isPrefixOf(artifactLocation) && userHome.segmentCount() < alCount)
|| (userTemp.isPrefixOf(artifactLocation) && userTemp.segmentCount() < alCount)
|| (workspaceRoot.isPrefixOf(artifactLocation) && workspaceRoot.segmentCount() < alCount) || (filesRoot != null
&& filesRoot.isPrefixOf(artifactLocation) && filesRoot.segmentCount() < alCount)))
conflictRes = ConflictResolution.UPDATE;
}
FileUtils.prepareDestination(file, conflictRes, MonitorUtils.subMonitor(prepMon, 10));
// Make sure the destination is not completely
// empty.
//
if (file.list().length == 0) {
File mtFile = new File(file, ".mtlock"); //$NON-NLS-1$
try {
mtFile.createNewFile();
} catch (IOException e) {
throw BuckminsterException.wrap(e);
}
}
} else {
// Assume that we are downloading a file and that
// the file should
// be given this name.
//
if (fileExists) {
if (conflictRes == ConflictResolution.FAIL)
throw new FileUtils.DestinationNotEmptyException(file);
if (!file.delete() && file.exists())
throw new DeleteException(file);
}
}
if (fileExists && conflictRes == ConflictResolution.UPDATE)
updateCandidates.add(ci);
IReaderType readerType = getMaterializationReaderType(cr);
List<Materialization> readerGroup = perReader.get(readerType.getId());
if (readerGroup == null) {
readerGroup = new ArrayList<Materialization>();
perReader.put(readerType.getId(), readerGroup);
}
readerGroup.add(mat);
}
} catch (CoreException e) {
if (ci != null)
statistics.addFailed(ci);
if (!context.isContinueOnError())
throw e;
context.addRequestStatus(cr.getRequest(), e.getStatus());
}
}
prepMon.done();
CorePlugin plugin = CorePlugin.getDefault();
IProgressMonitor matMon = MonitorUtils.subMonitor(monitor, 900);
matMon.beginTask(null, perReader.keySet().size() * 2 + perReader.entrySet().size() * 100);
for (Map.Entry<String, List<Materialization>> entry : perReader.entrySet()) {
List<Materialization> rg = entry.getValue();
// Prepare the reader type - i.e. set up a view if necessary.
//
IReaderType readerType = plugin.getReaderType(entry.getKey());
matMon.subTask(NLS.bind(Messages.Preparing_type_0, readerType.getId()));
readerType.prepareMaterialization(rg, context, MonitorUtils.subMonitor(matMon, 8));
for (Materialization mi : rg) {
ComponentIdentifier ci = mi.getComponentIdentifier();
Resolution cr = resolutionPerID.get(ci);
matMon.subTask(ci.getName());
boolean success = false;
IComponentReader reader = readerType.getReader(cr, context, MonitorUtils.subMonitor(matMon, 20));
try {
IPath location = mi.getComponentLocation();
IProgressMonitor matSubMon = MonitorUtils.subMonitor(matMon, 80);
if (!location.hasTrailingSeparator() && location.toFile().isDirectory())
mi = new Materialization(location.addTrailingSeparator(), ci);
mi.store(sm);
reader.materialize(location, cr, context, matSubMon);
adjustedMinfos.add(mi);
success = true;
} catch (CoreException e) {
if (!context.isContinueOnError())
throw e;
context.addRequestStatus(cr.getRequest(), e.getStatus());
} finally {
IOUtils.close(reader);
IPath location = mi.getComponentLocation();
if (location.hasTrailingSeparator())
location.append(".mtlock").toFile().delete(); //$NON-NLS-1$
if (success) {
if (updateCandidates.contains(ci))
statistics.addUpdated(ci);
else
statistics.addReplaced(ci);
} else {
statistics.addFailed(ci);
mi.remove(sm);
}
}
}
}
matMon.done();
return adjustedMinfos;
} finally {
for (Resolution res : resolutions) {
ComponentIdentifier ci = res.getComponentIdentifier();
if (!statistics.isIncluded(ci))
statistics.addFailed(ci);
}
monitor.done();
}
}
protected IPath getArtifactLocation(MaterializationContext context, Resolution resolution) throws CoreException {
return context.getArtifactLocation(resolution);
}
}