/*******************************************************************************
* Copyright (c) May 18, 2011 Zend Technologies Ltd.
* 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
*******************************************************************************/
package org.zend.sdklib.internal.project;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.bind.PropertyException;
import org.zend.sdklib.application.ZendProject.TemplateApplications;
import org.zend.sdklib.descriptor.PackageDescription;
import org.zend.sdklib.descriptor.pkg.Package;
import org.zend.sdklib.internal.library.AbstractChangeNotifier;
import org.zend.sdklib.internal.utils.JaxbHelper;
import org.zend.sdklib.mapping.IMappingEntry.Type;
import org.zend.sdklib.mapping.IMappingLoader;
import org.zend.sdklib.mapping.IMappingModel;
import org.zend.sdklib.mapping.MappingModelFactory;
import org.zend.sdklib.project.DeploymentScriptTypes;
import org.zend.webapi.core.progress.BasicStatus;
import org.zend.webapi.core.progress.IChangeNotifier;
import org.zend.webapi.core.progress.StatusCode;
/**
* Project creation and update handling including descriptor, scripts and
* application resources
*/
public class ProjectResourcesWriter extends AbstractChangeNotifier {
private static final String DEFAULT_DOCROOT = "public";
private static final String DEFAULT_APP_DIR = "data";
public static final String DESCRIPTOR = "deployment.xml";
// properties of the subject project
private final String name;
/**
* @param name
* of the application
* @param path
* project root
* @param withScripts2
* true if scripts are added to the project
* @param WelcomePgae
* @param isZend
*/
public ProjectResourcesWriter(String name) {
super();
this.name = name;
}
public ProjectResourcesWriter(String name, IChangeNotifier notifier) {
super(notifier);
this.name = name;
}
public ProjectResourcesWriter(File projectPath) {
this(getProjectName(new File(projectPath, DESCRIPTOR)));
}
public ProjectResourcesWriter(File projectPath, IChangeNotifier notifier) {
this(getProjectName(new File(projectPath, DESCRIPTOR)), notifier);
}
/**
* Writing descriptor file to the root project
*
* @param id
* - name of the project
* @param withContent
* - whether to write other contents than scripts and descriptor
* @param withScripts
* - whether to write scripts
* @param destination
* - destination directory
* @throws IOException
* @throws JAXBException
* @throws PropertyException
*/
public void writeDescriptor(File destination) throws IOException,
PropertyException, JAXBException {
notifier.statusChanged(new BasicStatus(StatusCode.STARTING,
"Application Update", "Creating deployment descriptor...", -1));
File descrFile = new File(destination, DESCRIPTOR);
if (!descrFile.exists()) {
final boolean n = descrFile.createNewFile();
if (!n) {
throw new IOException("Error creating file "
+ destination.getAbsolutePath());
}
writeDescriptor(new FileOutputStream(descrFile));
}
updateDescriptor(descrFile);
notifier.statusChanged(new BasicStatus(StatusCode.STOPPING,
"Application Update", "Creating deployment descriptor..."));
}
/**
* Writing project descriptor to a given output stream
*
* @param outputStream
* @throws IOException
* @throws PropertyException
* @throws JAXBException
*/
public void writeDescriptor(OutputStream outputStream) throws IOException,
PropertyException, JAXBException {
if (name == null) {
throw new IllegalArgumentException(
"Failed to create deployment descriptor. Project name is missing");
}
DescriptorWriter w = new DescriptorWriter(xmlEscape(name), DEFAULT_APP_DIR,
null, "1.0.0");
w.write(outputStream);
outputStream.close();
}
/**
* Writes scripts under destination with a given list of scripts (all or
* nothing are
*
* @param path
* @param withScripts
* @throws IOException
* @throws JAXBException
*/
public void writeScriptsByName(File container, String withScripts)
throws IOException, JAXBException {
if (withScripts == null) {
return;
}
notifier.statusChanged(new BasicStatus(StatusCode.STARTING,
"Application Update", "Creating deployment scripts...", -1));
File destination = getScriptsDirectory(container);
if (destination != null && !destination.isDirectory()) {
destination.mkdirs();
}
final ScriptsWriter w = new ScriptsWriter();
if ("all".equals(withScripts)) {
w.writeAllScripts(destination);
return;
}
String[] scriptsNames = withScripts.split(":");
for (String scriptName : scriptsNames) {
final DeploymentScriptTypes n = DeploymentScriptTypes
.byName(scriptName.trim());
if (n != null) {
w.writeSpecificScript(destination, n);
} else {
throw new IllegalArgumentException(MessageFormat.format(
"script with name {0} cannot be found", withScripts));
}
}
notifier.statusChanged(new BasicStatus(StatusCode.STOPPING,
"Application Update", "Creating deployment scripts..."));
}
public void writeDeploymentProperties(File container, IMappingLoader loader, boolean isUpdate)
throws IOException, JAXBException {
File exisitngMapping = new File(container,
MappingModelFactory.DEPLOYMENT_PROPERTIES);
if (exisitngMapping.exists() && !isUpdate) {
return;
}
IMappingModel model = loader == null ? MappingModelFactory
.createDefaultModel(container) : MappingModelFactory
.createModel(loader, container);
if (!isUpdate) {
notifier.statusChanged(new BasicStatus(StatusCode.STARTING,
"Application Update",
"Creating default deployment.properites file...", -1));
} else {
notifier.statusChanged(new BasicStatus(StatusCode.STARTING,
"Application Update",
"Updating deployment.properites file...", -1));
}
if (container.isDirectory()) {
String scriptdir = getScriptsDirectory(container).getName();
File[] files = container.listFiles();
for (File file : files) {
String name = file.getName();
if (!model.isExcluded(null, name) && !shoudBeExcluded(name)) {
if (name.equals(scriptdir) && file.isDirectory()) {
String[] scripts = file.list();
if (scripts != null) {
for (String script : scripts) {
String path = name + "/" + script;
model.addMapping(IMappingModel.SCRIPTSDIR, Type.INCLUDE, path,
false);
}
}
} else {
if (isUpdate == false) {
model.addMapping(IMappingModel.APPDIR, Type.INCLUDE, name, false);
}
}
}
}
model.store();
notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Application Update",
"Creating default deployment.properites file..."));
}
}
private boolean shoudBeExcluded(String name) {
return ProjectResourcesWriter.DESCRIPTOR.equals(name)
|| name.toLowerCase().contains("test") || name.startsWith(".");
}
private void updateDescriptor(File descrFile) throws IOException, JAXBException {
final FileInputStream packageStream = new FileInputStream(descrFile);
PackageDescription desc = new PackageDescription(packageStream);
File scriptsDir = findExistingScripts(descrFile.getParentFile());
String scripts = scriptsDir != null ? scriptsDir.getName() : null;
boolean isDirty = false;
File docroot = findPublicFolder(descrFile.getParentFile());
if (scripts != null && !scripts.equals(desc.getPackage().getScriptsdir())) {
desc.getPackage().setScriptsdir(scripts);
isDirty = true;
}
if (docroot != null && !docroot.getName().equals(desc.getPackage().getDocroot())) {
desc.getPackage().setDocroot(DEFAULT_APP_DIR +"/" + DEFAULT_DOCROOT);
isDirty = true;
}
packageStream.close();
if (isDirty) {
final FileOutputStream printStream = new FileOutputStream(descrFile);
JaxbHelper.marshalPackage(printStream, desc.getPackage());
printStream.close();
}
}
private File getScriptsDirectory(File container) throws IOException {
Package pkg = getPackage(container);
String scriptFolder = null;
if (pkg == null) {
// try to find existing scripts
File existingFolder = findExistingScripts(container);
if (existingFolder != null) {
return existingFolder;
}
} else {
scriptFolder = pkg.getScriptsdir();
}
if (scriptFolder == null || scriptFolder.isEmpty()) {
scriptFolder = "scripts";
}
return new File(container, scriptFolder);
}
private File findExistingScripts(File root) throws IOException {
if (root.isDirectory()) {
File[] files = root.listFiles();
for (File file : files) {
File result = findExistingScripts(file);
if (result != null) {
return result;
}
}
} else {
DeploymentScriptTypes[] types = DeploymentScriptTypes.values();
for (DeploymentScriptTypes type : types) {
if (type.getFilename().equals(root.getName())) {
return root.getParentFile();
}
}
}
return null;
}
private File findPublicFolder(File root) throws IOException {
if (root.isDirectory()) {
File[] files = root.listFiles();
for (File file : files) {
if (file.isDirectory() && file.getName().equals(DEFAULT_DOCROOT)) {
return file;
}
}
}
return null;
}
private static Package getPackage(File container) {
File descriptor = new File(container, ProjectResourcesWriter.DESCRIPTOR);
Package pkg;
try {
FileInputStream stream = new FileInputStream(descriptor);
pkg = JaxbHelper.unmarshalPackage(stream);
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
throw new IllegalArgumentException("Error reading descriptor file "
+ descriptor.getAbsolutePath(), e);
} catch (JAXBException e) {
throw new IllegalArgumentException("Error reading descriptor file "
+ descriptor.getAbsolutePath(), e);
}
if (pkg == null) {
throw new IllegalArgumentException("Error reading descriptor file "
+ descriptor.getAbsolutePath());
}
return pkg;
}
private static String getProjectName(File descriptor) {
Package pkg = getPackage(descriptor.getParentFile());
if (pkg == null) {
// no descriptor file - choose project name as directory name
final File parentFile = descriptor.getParentFile();
return parentFile.getName();
}
return pkg.getName();
}
/**
* Writes an application to a given destination directory
*
* @param destination
* @param app
* @throws IOException
*/
public void writeApplication(File destination, TemplateApplications app)
throws IOException {
final List<String> allResources = getAllResources(app);
for (String path : allResources) {
// file handling
if (!path.endsWith("/")) {
copyFile(destination, path, app);
} else {
createFolder(destination, path, app);
}
}
}
private boolean createFolder(File destination, String path,
TemplateApplications app) {
final File file = new File(destination, relativeToApp(path, app));
return file.mkdirs();
}
private void copyFile(File destination, String path,
TemplateApplications app) throws IOException, FileNotFoundException {
if (path.length() == 0) {
return;
}
final InputStream is = this.getClass().getResourceAsStream(path);
if (is == null) {
throw new IOException(path + " path is not found");
}
File outputFile = new File(destination, relativeToApp(path, app));
// create canonical structure
outputFile.getParentFile().mkdirs();
if (!outputFile.createNewFile()) {
throw new IOException("Cannot create file "
+ outputFile.getAbsolutePath());
}
InputOutputResource ior = new InputOutputResource(is,
new FileOutputStream(outputFile));
ior.copy();
}
private String relativeToApp(String path, TemplateApplications app) {
final int length = app.getBasePath().length();
return path.substring(length);
}
private List<String> getAllResources(TemplateApplications app)
throws IOException {
final InputStream iStream = this.getClass().getResourceAsStream(
app.getMap());
BufferedReader reader = new BufferedReader(new InputStreamReader(
iStream));
List<String> paths = new ArrayList<String>();
String readLine = reader.readLine();
while (readLine != null) {
paths.add(readLine.trim());
readLine = reader.readLine();
}
return paths;
}
private String xmlEscape(String name) {
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (c == '&' || c == '<' || c == '>') {
return "<![CDATA[" + name.replaceAll("]]>", "]]>]]><![CDATA[")
+ "]]>";
}
}
return name;
}
}