org.wso2.carbon.registry.synchronization.operation.UpdateCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.registry.synchronization.operation.UpdateCommand.java

Source

/*
 * 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.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.registry.core.Registry;
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.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * This command is used to perform a update operation which will download any changes to the
 * resources and collections from the provided registry instance into a checked out set of files and
 * directories local filesystem.
 */
@SuppressWarnings({ "ResultOfMethodCallIgnored", "unused" })
public class UpdateCommand {
    private static final Log log = LogFactory.getLog(UpdateCommand.class);

    private static boolean ignoreConflicts = Boolean
            .parseBoolean(System.getProperty(SynchronizationConstants.REGISTRY_IGNORE_CONFLICTS, "false"));

    private String registryUrl = null;
    private String outputFile = null;
    private String workingLocation = null;
    private String checkOutPath = null;
    private String username = null;
    private boolean cleanRegistry = false;
    private boolean isSilentUpdate = false;
    private boolean dumpLite = true;

    ////////////////////////////////////////////////////////
    // Fields maintaining status of command execution
    ////////////////////////////////////////////////////////

    private int addedCount = 0;
    private int updatedCount = 0;
    private int conflictedCount = 0;
    private int deletedCount = 0;
    private int notDeletedCount = 0;
    private boolean updated = false;

    /**
     * Creates an instance of a update 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 workingLocation     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 isSilentUpdate whether this update requires user's intervention or not.
     * @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 UpdateCommand(String outputFile, String workingLocation, String userUrl, boolean isSilentUpdate,
            String username, boolean cleanRegistry) throws SynchronizationException {
        this.outputFile = outputFile;
        this.workingLocation = workingLocation;
        this.isSilentUpdate = isSilentUpdate;
        this.username = username;
        this.cleanRegistry = cleanRegistry;

        // now if the user url is different to the registry url we are going to consider that as well.
        if (userUrl != null) {
            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
            }
        } else {
            // get the update details form the meta element of the current checkout
            OMElement metaOMElement = Utils.getMetaOMElement(workingLocation);
            if (metaOMElement == null) {
                throw new SynchronizationException(MessageCode.CHECKOUT_BEFORE_UPDATE);
            }
            registryUrl = metaOMElement.getAttributeValue(new QName("registryUrl"));
            checkOutPath = metaOMElement.getAttributeValue(new QName("path"));
        }
    }

    /**
     * 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 updated.
     *
     * @return the count of files updated.
     */
    public int getUpdatedCount() {
        return updatedCount;
    }

    /**
     * Method to obtain the count of files conflicted.
     *
     * @return the count of files conflicted.
     */
    public int getConflictedCount() {
        return conflictedCount;
    }

    /**
     * Method to obtain the count of files deleted.
     *
     * @return the count of files deleted.
     */
    public int getDeletedCount() {
        return deletedCount;
    }

    /**
     * Method to obtain the count of files that were not deleted.
     *
     * @return the count of files that were not deleted.
     */
    public int getNotDeletedCount() {
        return notDeletedCount;
    }

    /**
     * Method to specify that an operation is a silent update (requires user intervention or not).
     *
     * @param silentUpdate whether this operation is a silent update.
     */
    public void setSilentUpdate(boolean silentUpdate) {
        isSilentUpdate = silentUpdate;
    }

    /**
     * Method to specify weather to use dump or dump lite
     *
     * @param dumpLite
     */
    public void setDumpLite(boolean dumpLite) {
        this.dumpLite = dumpLite;
    }

    /**
     * This method will execute the update 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 deleted 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 deleting an existing file or directory during operation. If
     *                 this parameter is null, the default behaviour of deleting the existing file
     *                 will be used.
     * @return whether file system updated
     *
     * @throws SynchronizationException if the operation failed.
     */
    public boolean execute(Registry registry, UserInputCallback callback) throws SynchronizationException {
        if (outputFile != null) {
            throw new SynchronizationException(MessageCode.OUTPUT_FILE_NOT_SUPPORTED);
        }

        // we are doing the update 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
                    log.debug(
                            "Starting to do registry dump for : " + checkOutPath + " with : " + tempFile.getName());
                    if (dumpLite) {
                        registry.dumpLite(checkOutPath, writer);
                    } else {
                        registry.dump(checkOutPath, writer);
                    }
                    log.debug("Registry dump completed for : " + checkOutPath + " in : " + tempFile.getName());
                } finally {
                    if (writer != null) {
                        writer.close();
                    }
                }
            } catch (RegistryException e) {
                throw new SynchronizationException(MessageCode.ERROR_IN_DUMPING_NO_RESOURCE_OR_NO_PERMISSION, e,
                        new String[] { "path: " + checkOutPath, "username: " + username,
                                "registry url: " + registryUrl });
            } catch (IOException e) {
                throw new SynchronizationException(MessageCode.ERROR_IN_CREATING_TEMP_FILE_FOR_DUMP);
            }
            // 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);
                    log.debug("Starting registry 'updateRecursively' for repository : " + checkOutPath + " with : "
                            + tempFile.getName());
                    updateRecursively(xmlReader, workingLocation, checkOutPath, callback);
                    log.debug("Update recursively completed for repository : " + checkOutPath + " with : "
                            + tempFile.getName());
                } 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);
            } catch (XMLStreamException e) {
                throw new SynchronizationException(MessageCode.ERROR_IN_READING_STREAM_OF_TEMP_FILE_OF_DUMP);
            }
        } finally {
            if (tempFile != null) {
                // Our intention here is to delete the temporary file. We are not bothered whether
                // this operation fails.
                deleteTempFileFailed = !FileUtils.deleteQuietly(tempFile);
            }
        }
        if (deleteTempFileFailed) {
            throw new SynchronizationException(MessageCode.ERROR_IN_CLEANING_UP,
                    new String[] { "file path: " + tempFile.getAbsolutePath() });
        }

        if (cleanRegistry && registryUrl == null) {
            Utils.cleanEmbeddedRegistry();
        }

        return updated;
    }

    /**
     * This method will execute the update command utilizing the various parameters passed when
     * creating the instance of the command.
     *
     * @param registry the registry instance to be used.
     *
     * @return whether file system updated
     *
     * @throws SynchronizationException if the operation failed.
     */
    public boolean execute(Registry registry) throws SynchronizationException {
        return execute(registry, null);
    }

    // Performs a recursive update operation.
    private void updateRecursively(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 isUpdating = false;
        boolean isConflicting = false;
        boolean collectionIsNotUpdated = false; // valid only for the collection.
        String updatingMD5 = null;
        Iterator children = root.getChildren();
        while (children.hasNext()) {
            OMElement child = (OMElement) children.next();
            String localName = child.getLocalName();

            // get content
            if (localName.equals("content")) {
                OMText text = (OMText) child.getFirstOMChild();
                // we keep content as base64 encoded
                if (text != null) {
                    contentBytes = Base64.decode(text.getText());
                }
                updatingMD5 = Utils.getMD5(contentBytes);
                root.addAttribute("md5", updatingMD5, null);
                child.detach();
            }
        }

        // access the meta info of the current
        String metaFilePath;

        if (isCollection) {
            metaFilePath = filePath + File.separator + SynchronizationConstants.META_DIRECTORY + File.separator
                    + SynchronizationConstants.META_FILE_PREFIX + SynchronizationConstants.META_FILE_EXTENSION;
        } else {
            String parentDirName = file.getAbsoluteFile().getParent();
            metaFilePath = parentDirName + File.separator + SynchronizationConstants.META_DIRECTORY + File.separator
                    + SynchronizationConstants.META_FILE_PREFIX + Utils.encodeResourceName(name)
                    + SynchronizationConstants.META_FILE_EXTENSION;
        }

        File metaFile = new File(metaFilePath);

        if (file.exists()) {
            isUpdating = true;
            if (isCollection != file.isDirectory()) {
                throw new SynchronizationException(MessageCode.COLLECTION_AND_RESOURCE_SAME_NAME,
                        new String[] { "file name: " + filePath });
            }

            if (metaFile.exists()) {
                // we need to check the last updated times of the current resource
                // and the updating resource

                OMElement updatingVersionElement = root.getFirstChildWithName(new QName("version"));
                if (updatingVersionElement == null) {
                    throw new SynchronizationException(MessageCode.CHECKOUT_OLD_VERSION,
                            new String[] { "missing element: version", "path: " + path });
                }
                String updatingVersionStr = updatingVersionElement.getText();

                // get the meta file OMElement
                OMElement metaFileElement = Utils.getOMElementFromMetaFile(metaFilePath);

                OMElement metaFileVersionElement = metaFileElement.getFirstChildWithName(new QName("version"));

                String metaFileVersionStr;
                if (metaFileVersionElement == null) {
                    //Version not defined for the newly added files. They are added when updating.
                    metaFileVersionStr = updatingVersionStr;
                    root.addAttribute("version", metaFileVersionStr, null);
                    Utils.createMetaFile(metaFilePath, root);
                } else {
                    metaFileVersionStr = metaFileVersionElement.getText();
                }

                if (isCollection) {
                    if (metaFileVersionStr.equals(updatingVersionStr)) {
                        // so there is no server updates for the collection
                        Utils.createMetaFile(metaFilePath, root);
                        collectionIsNotUpdated = true;
                    }
                } else {
                    // here we not just check server side updates, but also check local changes using md5s
                    byte[] currentFileContent = Utils.getBytesFromFile(file);

                    String metaFileMD5 = metaFileElement.getAttributeValue(new QName("md5"));
                    String currentMD5 = Utils.getMD5(currentFileContent);

                    if (metaFileMD5 != null && metaFileMD5.equals(currentMD5)) {
                        // there is no modifications happens to the current file locally,
                        if (metaFileVersionStr.equals(updatingVersionStr)) {
                            // the file in the server is not updated, so just keep the current file locally.
                            // so we are only storing the meta information in the meta file.
                            Utils.createMetaFile(metaFilePath, root);
                            return;
                        }
                        // else:
                        // there is a server update to the file, so lets allow it to update the local one
                        // local one is not updated, so it will be overwritten by the server one
                    } else if (metaFileVersionStr.equals(updatingVersionStr)) {
                        // there is no server side changes, but there are client side changes,
                        // just don't update the content, but let the meta file get updated.
                        Utils.createMetaFile(metaFilePath, root);
                        return;
                    } else {
                        // this is the following scenario
                        // (!metaFileMD5.equals(currentMD5) &&
                        //    !metaFileVersionStr.equals(updatingVersionStr))
                        if (updatingMD5 != null && !updatingMD5.equals(currentMD5)) {
                            isConflicting = true;
                            root.addAttribute("md5", "", null);
                        }
                    }
                }
            } else if (!isCollection) {
                // if there is no meta file exists, that mean there is a conflict
                // a new resource is created both locally and in server
                isConflicting = true;
            }

            if (isConflicting && !isSilentUpdate && !ignoreConflicts) {
                // should rename the current file as file.mine
                String mineFileName = filePath + SynchronizationConstants.MINE_FILE_POSTFIX;
                File mineFile = new File(mineFileName);
                Utils.copy(file, mineFile);

                // updating the current file as versionedFileName
                String versionedFileName = filePath + SynchronizationConstants.SERVER_FILE_POSTFIX;
                file = new File(versionedFileName);

                // set the conflicting flag
                root.addAttribute("conflicting", "true", null);
            }
        } else if (isSilentUpdate) {
            // if no files exists locally, the silent update will return
            return;
        } else if (metaFile.exists()) {
            // now the meta file is there but the direct file is deleted, so we will
            // ask whether he want to get the up. Default behaviour is not to take the update from
            // the server. This is because the working copy is up-to-date.
            if (callback == null || callback.getConfirmation(
                    new Message(MessageCode.KEEP_DELETED_FILE, new String[] { filePath }),
                    SynchronizationConstants.DELETE_CONFIRMATION_CONTEXT)) {
                return;
            }
        }
        if (!isUpdating) {
            try {
                // Create file if it does not exist
                if (isCollection) {
                    boolean ignore = file.mkdir(); // ignores the return value purposely
                } else {
                    boolean ignore = file.createNewFile(); // ignores the return value purposely
                }
            } catch (IOException e) {
                throw new SynchronizationException(MessageCode.FILE_CREATION_FAILED, e,
                        new String[] { "file name: " + filePath });
            }
        }

        if (!isCollection) {
            FileOutputStream fileOutputStream = null;
            try {
                boolean writeToFile = true;

                if (file.exists()) {
                    byte[] currentContentBytes = Utils.getBytesFromFile(file);
                    if (currentContentBytes != null && contentBytes != null) {
                        String currentContentMd5 = Utils.getMD5(currentContentBytes);
                        String writingContentMd5 = Utils.getMD5(contentBytes);
                        if (writingContentMd5 != null && writingContentMd5.equals(currentContentMd5)) {
                            writeToFile = false;
                        }
                    }
                }
                if (writeToFile) {
                    fileOutputStream = new FileOutputStream(file);
                    fileOutputStream.write(contentBytes);
                    fileOutputStream.flush();
                    fileOutputStream.close();
                }

            } catch (IOException e) {
                throw new SynchronizationException(MessageCode.PROBLEM_IN_CREATING_CONTENT, e,
                        new String[] { "file name: " + filePath });
            } finally {
                try {
                    if (fileOutputStream != null) {
                        fileOutputStream.close();
                    }
                } catch (IOException e) {
                    log.error("Failed to close the stream", e);
                }
            }
        } else {
            // creating the meta directory
            String metaDirectoryName = filePath + 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 });
            }
        }

        boolean iterateChildren = true;

        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();
            }
            iterateChildren = false;
        }
        if (iterateChildren) {
            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")));
                    }
                    iterateChildren = false;
                    break;
                }
            } while (!xmlReader.isStartElement() && xmlReader.hasNext());
        }

        Map<String, Boolean> childNames = new HashMap<String, Boolean>();

        if (iterateChildren) {
            while (xmlReader.hasNext() && xmlReader.isStartElement()
                    && xmlReader.getLocalName().equals("resource")) {
                // prepare the children absolute path

                String childName = xmlReader.getAttributeValue(null, "name");
                String fileResourceName = childName;
                String childFilePath = filePath + File.separator + fileResourceName;
                String childPath = (path.equals("/") ? "" : path) + "/" + childName;

                updateRecursively(xmlReader, childFilePath, childPath, callback);
                childNames.put(fileResourceName, true);

                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();
            }

            // now we are checking which files have been deleted at the server end.
            String[] childFileNames = file.list();
            if (childFileNames != null) {
                for (String childFileName : childFileNames) {
                    if (childFileName.equals(SynchronizationConstants.META_DIRECTORY)) {
                        continue;
                    }
                    if (childNames.get(childFileName) != null && childNames.get(childFileName)) {
                        // this files stays on the server as well, so nothing to worry
                        continue;
                    }
                    // hm, we have a situation that stuff exist local, but not at the server
                    // first need to check whether they are newly added.
                    // we can do that by checking the existence of meta directory
                    String childFilePath = file + File.separator + childFileName;
                    File childFile = new File(file, childFileName);
                    boolean shouldDelete = false;
                    File childMetaFile;
                    if (childFile.isDirectory()) {
                        // the meta directory should exist in .meta
                        String metaDirName = filePath + File.separator + childFileName + File.separator
                                + SynchronizationConstants.META_DIRECTORY + File.separator + File.separator
                                + SynchronizationConstants.META_FILE_PREFIX
                                + SynchronizationConstants.META_FILE_EXTENSION;
                        childMetaFile = new File(metaDirName);

                        if (childMetaFile.exists()) {
                            // looks like it's bean earlier checkout from registry, mean it is now deleted
                            shouldDelete = true;
                        }
                    } else {
                        String metaFileName = filePath + File.separator + SynchronizationConstants.META_DIRECTORY
                                + File.separator + SynchronizationConstants.META_FILE_PREFIX
                                + Utils.encodeResourceName(childFileName)
                                + SynchronizationConstants.META_FILE_EXTENSION;
                        childMetaFile = new File(metaFileName);
                        if (childMetaFile.exists()) {
                            // looks like it's bean earlier checkout from registry, mean it is now deleted
                            shouldDelete = true;
                        }
                    }
                    if (shouldDelete && !isSilentUpdate) {
                        deleteFile(callback, childFilePath, childFile, childMetaFile);
                    }
                }
            }
        } else {
            //            Seems like the user has deleted all the resources under a particular collection

            String[] allFilesNames = file.list();
            if (allFilesNames != null) {
                for (String filesName : allFilesNames) {
                    String childFilePath;
                    File childFile;
                    File childMetaFile;

                    childFilePath = filePath + File.separator + filesName;
                    childFile = new File(childFilePath);
                    if (childFile.isDirectory()) {
                        //                    Here we are deleting all the collections that were deleted from the server
                        String childMetaCollectionPath = childFilePath + File.separator
                                + SynchronizationConstants.META_DIRECTORY;
                        childMetaFile = new File(childMetaCollectionPath);
                        if (childMetaFile.exists() && !isSilentUpdate) {
                            deleteFile(callback, childFilePath, childFile, childMetaFile);
                        }
                    } else {
                        //                    Here we remove all the resource that have been deleted from the server
                        String metaFileFullName = filePath + File.separator
                                + SynchronizationConstants.META_DIRECTORY + File.separator
                                + SynchronizationConstants.META_FILE_PREFIX + Utils.encodeResourceName(filesName)
                                + SynchronizationConstants.META_FILE_EXTENSION;

                        childMetaFile = new File(metaFileFullName);
                        if (childMetaFile.exists() && !isSilentUpdate) {
                            deleteFile(callback, childFilePath, childFile, childMetaFile);
                        }
                    }
                }
            }
        }

        if (file.isDirectory() && collectionIsNotUpdated) {
            return;
        }

        // creating the meta file
        String metaFileName;
        if (isCollection) {
            metaFileName = filePath + File.separator + SynchronizationConstants.META_DIRECTORY + File.separator
                    + SynchronizationConstants.META_FILE_PREFIX + SynchronizationConstants.META_FILE_EXTENSION;
        } else {
            String parentDirName = file.getAbsoluteFile().getParent();
            metaFileName = parentDirName + File.separator + SynchronizationConstants.META_DIRECTORY + File.separator
                    + SynchronizationConstants.META_FILE_PREFIX + Utils.encodeResourceName(name)
                    + SynchronizationConstants.META_FILE_EXTENSION;
        }
        Utils.createMetaFile(metaFileName, root);

        // printing out the information of the file
        if (isConflicting) {
            if (callback != null && !isSilentUpdate) {
                callback.displayMessage(
                        new Message(MessageCode.CONFLICTED, new String[] { refinedPathToPrint(filePath) }));
            }
            conflictedCount++;
        } else if (isUpdating) {
            if (callback != null && !isSilentUpdate) {
                callback.displayMessage(
                        new Message(MessageCode.UPDATED, new String[] { refinedPathToPrint(filePath) }));
            }
            updatedCount++;
            updated = true;
        } else {
            if (callback != null && !isSilentUpdate) {
                callback.displayMessage(
                        new Message(MessageCode.ADDED, new String[] { refinedPathToPrint(filePath) }));
            }
            addedCount++;
            updated = true;
        }
    }

    private void deleteFile(UserInputCallback callback, String childFilePath, File childFile, File childMetaFile)
            throws SynchronizationException {
        boolean isDeleted = Utils.confirmDelete(childFile, childMetaFile, callback);
        if (isDeleted) {
            if (callback != null && !isSilentUpdate) {
                callback.displayMessage(
                        new Message(MessageCode.DELETED, new String[] { refinedPathToPrint(childFilePath) }));
            }
            deletedCount++;
            updated = true;
        } else {
            if (callback != null && !isSilentUpdate) {
                callback.displayMessage(
                        new Message(MessageCode.NOT_DELETED, new String[] { refinedPathToPrint(childFilePath) }));
            }
            notDeletedCount++;
        }
    }

    // Method to obtain a refined path from the given path. In here, ".." will be replaced with the
    // correct name of the directory.
    private static String refinedPathToPrint(String path) {
        StringTokenizer tokenizer = new StringTokenizer(path, "/");

        StringBuilder refinedPath = new StringBuilder("");
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            // if .. found ignore the next element
            if (!token.equals("..")) {
                refinedPath.append(token);
                if (tokenizer.hasMoreElements()) {
                    refinedPath.append("/");
                }
            }
        }
        return refinedPath.toString();
    }
}