/*
* Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.wso2.carbon.registry.synchronization.operation;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.util.Base64;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.synchronization.SynchronizationConstants;
import org.wso2.carbon.registry.synchronization.SynchronizationException;
import org.wso2.carbon.registry.synchronization.UserInputCallback;
import org.wso2.carbon.registry.synchronization.Utils;
import org.wso2.carbon.registry.synchronization.message.Message;
import org.wso2.carbon.registry.synchronization.message.MessageCode;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* This command is used to perform a check-out operation which will download the resources and
* collections from the provided registry instance into the local filesystem.
*/
@SuppressWarnings({"ResultOfMethodCallIgnored", "unused"})
public class CheckOutCommand {
private String outputFile = null;
private String checkOutPath = null;
private String registryUrl = null;
private String workingDir = null;
private String username = null;
private boolean cleanRegistry = false;
////////////////////////////////////////////////////////
// Fields maintaining status of command execution
////////////////////////////////////////////////////////
private int addedCount = -1;
private int overwrittenCount = 0;
private int nonOverwrittenCount = 0;
/**
* Creates an instance of a check-out command which can be executed against a provided registry
* instance.
*
* @param outputFile if the content is to be downloaded into a single meta file, this
* parameter can be used to specify the path to the meta file.
* @param workingDir if the content is to be downloaded into a directory on the filesystem,
* this parameter can be used to specify the path to the corresponding
* location.
* @param userUrl aggregate URL containing a concatenation of the registry URL and the
* resource path that is capable of referencing a remote resource. This url
* will contain only the resource path if the resource was local to the
* given registry instance.
* @param username the name of the user (which should be a valid username on the target
* server on which the provided registry instance is running) that performs
* this operation.
* @param cleanRegistry whether the embedded registry instance must be cleaned after the
* execution of the operation.
*
* @throws SynchronizationException if the operation failed.
*/
public CheckOutCommand(String outputFile,
String workingDir,
String userUrl,
String username,
boolean cleanRegistry) throws SynchronizationException {
this.outputFile = outputFile;
this.workingDir = workingDir;
this.username = username;
this.cleanRegistry = cleanRegistry;
if (userUrl == null) {
throw new SynchronizationException(MessageCode.CO_PATH_MISSING);
}
// derive the registry url and the path
registryUrl = Utils.getRegistryUrl(userUrl);
checkOutPath = Utils.getPath(userUrl);
if (checkOutPath == null || checkOutPath.equals("")) {
checkOutPath = "/";
// we are converting the root path to the current directory of the file system
}
}
/**
* Method to obtain the count of files added.
*
* @return the count of files added.
*/
public int getAddedCount() {
return addedCount;
}
/**
* Method to obtain the count of files overwritten.
*
* @return the count of files overwritten.
*/
public int getOverwrittenCount() {
return overwrittenCount;
}
/**
* Method to obtain the count of files that were not overwritten.
*
* @return the count of files that were not overwritten.
*/
public int getNonOverwrittenCount() {
return nonOverwrittenCount;
}
/**
* This method will execute the check-out command utilizing the various parameters passed when
* creating the instance of the command. This method accepts the users preference to whether a
* file or directory should be overwritten on the filesystem.
*
* @param registry the registry instance to be used.
* @param callback the instance of a callback that can be used to determine the user's
* preference before overwriting an existing file or directory during operation.
* If this parameter is null, the default behaviour of overwriting the existing
* file will be used.
*
* @throws SynchronizationException if the operation failed.
*/
public void execute(Registry registry, UserInputCallback callback)
throws SynchronizationException {
if (outputFile != null) {
dumpToFile(registry);
} else {
dumpToFileSystem(registry, callback);
}
}
/**
* This method will execute the check-out command utilizing the various parameters passed when
* creating the instance of the command.
*
* @param registry the registry instance to be used.
*
* @throws SynchronizationException if the operation failed.
*/
public void execute(Registry registry) throws SynchronizationException {
execute(registry, null);
}
// Downloads the given resources and collections into a dump file.
private void dumpToFile(Registry registry) throws SynchronizationException {
String outputXml = outputFile + SynchronizationConstants.META_FILE_EXTENSION;
if (workingDir != null) {
outputFile = workingDir + File.separator + outputFile;
}
try {
if (!registry.resourceExists(checkOutPath)) {
throw new SynchronizationException(
MessageCode.ERROR_IN_DUMPING_NO_RESOURCE_OR_NO_PERMISSION,
new String[]{"path: " + checkOutPath, "username: " + username});
}
} catch (Exception e) {
throw new SynchronizationException(MessageCode.ERROR_IN_DUMPING_AUTHORIZATION_FAILED, e,
new String[]{"path: " + checkOutPath, "username: " + username});
}
try {
// we don't care what is dumping..
// doing the dump
ZipOutputStream zos = new
ZipOutputStream(new FileOutputStream(outputFile));
ZipEntry ze = new ZipEntry(outputXml);
ze.setMethod(ZipEntry.DEFLATED);
zos.putNextEntry(ze);
Writer zipWriter = new OutputStreamWriter(zos);
registry.dump(checkOutPath, zipWriter);
zos.close();
} catch (Exception e) {
throw new SynchronizationException(MessageCode.ERROR_IN_DUMPING, e,
new String[]{"path: " + checkOutPath, "username: " + username});
}
if (cleanRegistry && registryUrl == null) {
Utils.cleanEmbeddedRegistry();
}
}
// Downloads the given resources and collections into files and folders on the filesystem.
private void dumpToFileSystem(Registry registry, UserInputCallback callback)
throws SynchronizationException {
addedCount = 0;
// first try getting the path and confirmed it is a collection
Resource r;
try {
r = registry.get(checkOutPath);
} catch (Exception e) {
throw new SynchronizationException(
MessageCode.ERROR_IN_DUMPING_NO_RESOURCE_OR_NO_PERMISSION,
new String[]{"path: " + checkOutPath, "username: " + username});
}
if (!(r instanceof Collection)) {
throw new SynchronizationException(MessageCode.DUMPING_NON_COLLECTION,
new String[]{"path: " + checkOutPath, "username: " + username});
}
// we are doing the checkout through a temp file. (so assumed enough spaces are there)
File tempFile = null;
boolean deleteTempFileFailed = false;
FileWriter writer = null;
try {
try {
tempFile = File.createTempFile(SynchronizationConstants.DUMP_META_FILE_NAME,
SynchronizationConstants.META_FILE_EXTENSION);
try {
writer = new FileWriter(tempFile);
// doing the dump
registry.dump(checkOutPath, writer);
} finally {
if (writer != null) {
writer.close();
}
}
} catch (RegistryException e) {
throw new SynchronizationException(
MessageCode.ERROR_IN_DUMPING_NO_RESOURCE_OR_NO_PERMISSION,
new String[]{"path: " + checkOutPath, "username: " + username});
} catch (IOException e) {
throw new SynchronizationException(
MessageCode.ERROR_IN_CREATING_TEMP_FILE_FOR_DUMP,
e);
}
// now read the xml stream from the file
XMLStreamReader xmlReader = null;
Reader reader = null;
try {
try {
reader = new FileReader(tempFile);
xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(reader);
checkOutRecursively(xmlReader, workingDir, checkOutPath, callback);
} finally {
try {
if (xmlReader != null) {
xmlReader.close();
}
} finally {
if (reader != null) {
reader.close();
}
}
}
} catch (IOException e) {
throw new SynchronizationException(
MessageCode.ERROR_IN_READING_TEMP_FILE_OF_DUMP, e);
} catch (XMLStreamException e) {
throw new SynchronizationException(
MessageCode.ERROR_IN_READING_STREAM_OF_TEMP_FILE_OF_DUMP, e);
}
} finally {
if (tempFile != null) {
// Our intention here is to delete the temporary file. We are not bothered whether
// this operation fails.
deleteTempFileFailed = !tempFile.delete();
}
}
if (deleteTempFileFailed) {
throw new SynchronizationException(MessageCode.ERROR_IN_CLEANING_UP,
new String[]{"file path: " + tempFile.getAbsolutePath()});
}
if (cleanRegistry && registryUrl == null) {
Utils.cleanEmbeddedRegistry();
}
}
// Performs a recursive check-out operation.
private void checkOutRecursively(XMLStreamReader xmlReader,
String filePath,
String path,
UserInputCallback callback)
throws SynchronizationException, XMLStreamException {
// we will first generate the axiom node from the reader,
OMElement root = Utils.readMetaElement(xmlReader);
// adding path and the registryUrl
root.addAttribute("path", path, null);
if (registryUrl != null) {
root.addAttribute("registryUrl", registryUrl, null);
}
String isCollectionString = root.getAttributeValue(new QName("isCollection"));
boolean isCollection = isCollectionString.equals("true");
String name = root.getAttributeValue(new QName("name"));
byte[] contentBytes = new byte[0];
File file = new File(filePath);
boolean overwrite = true;
boolean fileAlreadyExist = false;
if (!isCollection && file.exists()) {
fileAlreadyExist = true;
// File already exists, we are asking whether we will skip the file or overwrite it.
// The default behaviour is to overwrite.
if (callback != null &&
!callback.getConfirmation(new Message(
MessageCode.FILE_OVERWRITE_CONFIRMATION,
new String[]{filePath}),
SynchronizationConstants.OVERWRITE_CONFIRMATION_CONTEXT)) {
overwrite = false;
}
}
try {
// Create file if it does not exist
if (isCollection) {
boolean ignore = file.mkdir(); // ignores the return value purposely
} else if (overwrite) {
boolean ignore = file.createNewFile(); // ignores the return value purposely
}
} catch (IOException e) {
throw new SynchronizationException(MessageCode.FILE_CREATION_FAILED, e,
new String[]{
"file: " + filePath});
}
// we are extracting the content from the meta element.
Iterator children = root.getChildren();
while (children.hasNext()) {
OMElement child = (OMElement) children.next();
String localName = child.getLocalName();
// LastModified
if (localName.equals("lastModified")) {
OMText text = (OMText) child.getFirstOMChild();
if (text != null) {
long date = Long.parseLong(text.getText());
// We are not bothered whether this failed to set the last-modified time. If we
// cannot modify the file, we would fail when attempting to write to it anyway.
boolean ignore = file.setLastModified(date);
}
}
// get content
else if (localName.equals("content")) {
OMText text = (OMText) child.getFirstOMChild();
// we keep content as base64 encoded
if (text != null) {
contentBytes = Base64.decode(text.getText());
}
String md5 = Utils.getMD5(contentBytes);
root.addAttribute("md5", md5, null);
child.detach();
}
}
if (!isCollection && overwrite) {
try {
FileOutputStream fileStream = null;
try {
fileStream = new FileOutputStream(file);
fileStream.write(contentBytes);
fileStream.flush();
} finally {
if (fileStream != null) {
fileStream.close();
}
}
} catch (IOException e) {
throw new SynchronizationException(MessageCode.PROBLEM_IN_CREATING_CONTENT,
e,
new String[]{"file: " + filePath});
}
}
String parentDirName = file.getParent();
// creating the meta directory
String metaDirectoryName;
if (isCollection) {
metaDirectoryName = filePath + File.separator + SynchronizationConstants.META_DIRECTORY;
} else {
metaDirectoryName =
parentDirName + File.separator + SynchronizationConstants.META_DIRECTORY;
}
File metaDirectory = new File(metaDirectoryName);
if (!metaDirectory.exists() && !metaDirectory.mkdir()) {
throw new SynchronizationException(MessageCode.ERROR_CREATING_META_FILE,
new String[]{"file: " + metaDirectoryName});
}
// creating the meta file
String metaFilePath;
if (isCollection) {
metaFilePath = filePath + File.separator + SynchronizationConstants.META_DIRECTORY +
File.separator +
SynchronizationConstants.META_FILE_PREFIX +
SynchronizationConstants.META_FILE_EXTENSION;
} else {
metaFilePath =
parentDirName + File.separator + SynchronizationConstants.META_DIRECTORY +
File.separator + SynchronizationConstants.META_FILE_PREFIX +
Utils.encodeResourceName(name) +
SynchronizationConstants.META_FILE_EXTENSION;
}
Utils.createMetaFile(metaFilePath, root);
// printing out the information of the file
if (!fileAlreadyExist) {
if (callback != null) {
callback.displayMessage(new Message(MessageCode.ADDED, new String[]{filePath}));
}
addedCount++;
} else {
if (overwrite) {
if (callback != null) {
callback.displayMessage(
new Message(MessageCode.OVERWRITTEN, new String[]{filePath}));
}
overwrittenCount++;
} else {
if (callback != null) {
callback.displayMessage(
new Message(MessageCode.NON_OVERWRITTEN, new String[]{filePath}));
}
nonOverwrittenCount++;
}
}
if (!xmlReader.hasNext() || !(xmlReader.isStartElement() &&
xmlReader.getLocalName().equals("children"))) {
// finished the recursion
// consuming the stream until the resource end element found
while (xmlReader.hasNext() && !(xmlReader.isEndElement() &&
xmlReader.getLocalName().equals("resource"))) {
xmlReader.next();
}
return;
}
do {
xmlReader.next();
if (xmlReader.isEndElement() && xmlReader.getLocalName().equals("children")) {
// this means empty children, just quit from here
// before that we have to set the cursor to the end of the current resource
if (xmlReader.hasNext()) {
do {
xmlReader.next();
} while (xmlReader.hasNext() && !(xmlReader.isEndElement() &&
xmlReader.getLocalName().equals("resource")));
}
return;
}
} while (!xmlReader.isStartElement() && xmlReader.hasNext());
while (xmlReader.hasNext() && xmlReader.isStartElement() &&
xmlReader.getLocalName().equals("resource")) {
// prepare the children absolute path
String childName = xmlReader.getAttributeValue(null, "name");
String fileResourceName = Utils.encodeResourceName(childName);
String childFilePath = filePath + File.separator + fileResourceName;
String childPath = (path.equals("/") ? "" : path) + "/" + childName;
checkOutRecursively(xmlReader, childFilePath, childPath, callback);
while ((!xmlReader.isStartElement() && xmlReader.hasNext()) &&
!(xmlReader.isEndElement() && xmlReader.getLocalName().equals("children"))) {
xmlReader.next();
}
if (xmlReader.isEndElement() && xmlReader.getLocalName().equals("children")) {
// we are in the end of the children tag.
break;
}
}
// consuming the stream until the resource end element found
while (xmlReader.hasNext() && !(xmlReader.isEndElement() &&
xmlReader.getLocalName().equals("resource"))) {
xmlReader.next();
}
}
}