/*******************************************************************************
* Copyright (c) 2013 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.wizard.template.infrastructure;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Bundle;
import org.springframework.ide.eclipse.wizard.WizardPlugin;
import org.springsource.ide.eclipse.commons.content.core.ContentItem;
import org.springsource.ide.eclipse.commons.content.core.ContentManager;
import org.springsource.ide.eclipse.commons.core.ZipFileUtil;
/**
* Loads content defined by descriptors for creating projects. These
* descriptors, and the resources that they point to, are contained within an
* Eclipse bundle, and referenced by relative file URLs.
*/
public class BundleContentLoader {
private final Bundle bundle;
private final ContentItem contentItem;
private final SimpleProjectContentManager contentManager;
public BundleContentLoader(ContentItem contentItem, Bundle bundle, SimpleProjectContentManager contentManager) {
this.contentItem = contentItem;
this.bundle = bundle;
this.contentManager = contentManager;
}
/**
* Unzip (load) contents of a project creation descriptor into a
* installation directory pointed to by a content manager. If the contents
* are already unzipped , it will not unzip again. The contents of the
* descriptor are unzipped in a subdirectory with a name containing the
* descriptor name and version number. If a directory with that same name
* already exists, and contains at least one descriptor file, it will not
* unzip the contents. Therefore, to install newer versions of the same
* descriptor, make sure that the version number is changed in the
* descriptor definition.
* @param monitor
* @throws CoreException
*/
public void load(IProgressMonitor monitor) throws CoreException {
SubMonitor progress = SubMonitor.convert(monitor, 20);
try {
File baseInstallDirectory = contentManager.getInstallDirectory();
if (baseInstallDirectory == null || !baseInstallDirectory.exists()) {
throw new CoreException(
new Status(
IStatus.ERROR,
WizardPlugin.PLUGIN_ID,
NLS.bind(
"Installation directory for bundled contents in bundle {0} does not exist. Unable to extract contents to local directory.",
WizardPlugin.getDefault().getBundle().getSymbolicName())));
}
File directory = new File(baseInstallDirectory, contentItem.getPath());
// If it already exists, do not load it again
if (contentManager.exists(directory)) {
return;
}
String url = contentItem.getLocalDescriptor().getUrl();
InputStream bundleResourceInput = null;
// This may be a relative file URL for a resource in the bundle
try {
URI uri = new URI(url);
if (bundle != null && uri.getScheme().startsWith("file")) {
IPath path = new Path(uri.getPath());
bundleResourceInput = FileLocator.openStream(WizardPlugin.getDefault().getBundle(), path, false);
}
}
catch (URISyntaxException e) {
String message = NLS.bind(
"Incorrect URI for resource {0} in bundle {1}. Unable to load template data.", url,
bundle.getLocation());
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID, message, e));
}
catch (IOException e) {
String message = NLS.bind(
"I/O error. Failed to read resource {0} in bundle {1}. Unable to load template data.", url,
bundle.getLocation());
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID, message, e));
}
File archiveFile = new File(baseInstallDirectory, contentItem.getPath() + ContentManager.ARCHIVE_EXTENSION);
if (bundleResourceInput != null) {
// Loading also closes the input stream
load(bundleResourceInput, archiveFile, directory, monitor);
}
else {
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID,
"Unable to create input stream to descriptor files in bundle: "
+ WizardPlugin.getDefault().getBundle().getSymbolicName()));
}
}
finally {
progress.done();
}
}
/**
* This operation does several things:
*
* <p/>
* 1. Copies a zip file containing template files (e.g., template.xml,
* template.zip, and wizard.json) from the bundle into a local specified
* directory
* <p/>
* 2. Unzips the copied local archive file into that same directory
* <p/>
* 3. Deletes the copied local archive
* @param in inputstream to the archive file in the bundle
* @param localArchiveFile archive file where the contents of the the
* inputstream should be written to. In other words, this is the local copy
* of the zip file in the bundle
* @param directory directory where local archive file is located as well as
* where the contents of the local archive file are unzipped
* @param monitor
*/
protected void load(InputStream in, File localArchiveFile, File directory, IProgressMonitor monitor)
throws CoreException {
SubMonitor progress = SubMonitor.convert(monitor, 100);
if (!directory.exists()) {
directory.mkdirs();
}
OutputStream out = null;
// Copy zip file from bundle to local zip file first. IMPORTANT: close
// the streams first to flush the buffers before attempting to read the
// local zip file, as for
// zip files that are too small, all the contents may be in the output
// buffer and not written to the local zip file
try {
out = new BufferedOutputStream(new FileOutputStream(localArchiveFile));
byte[] buf = new byte[40 * 1024];
int read;
while ((read = in.read(buf)) >= 0) {
if (read > 0) {
out.write(buf, 0, read);
}
}
}
catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID,
"I/O error while copying template zip file " + localArchiveFile.getAbsolutePath() + " to "
+ directory.getAbsolutePath() + " ---- " + e.getMessage(), e));
}
finally {
try {
// Close the output stream first to flush the contents into the
// local zip file.
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
}
catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID,
"I/O error while copying template zip file " + localArchiveFile.getAbsolutePath() + " to "
+ directory.getAbsolutePath() + " ---- " + e.getMessage(), e));
}
}
// Now that the zip file has been copied , unzip its contents
try {
URL fileUrl = localArchiveFile.toURI().toURL();
ZipFileUtil.unzip(fileUrl, directory, progress.newChild(30));
if (directory.listFiles().length <= 0) {
String message = NLS.bind("Zip file {0} appears to be empty", localArchiveFile);
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID, message));
}
}
catch (MalformedURLException e) {
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID,
"Unable to unzip template content due to malformed URL: " + e.getMessage(), e));
}
catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, WizardPlugin.PLUGIN_ID,
"I/O error while unzipping template zip file " + localArchiveFile.getAbsolutePath() + " into "
+ directory.getAbsolutePath() + " --- " + e.getMessage(), e));
}
finally {
// Once unzipped, the copied archive file is no longer needed
localArchiveFile.delete();
}
}
}