com.mediaworx.intellij.opencmsplugin.sync.VfsAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.mediaworx.intellij.opencmsplugin.sync.VfsAdapter.java

Source

/*
 * This file is part of the OpenCms plugin for IntelliJ by mediaworx.
 *
 * For further information about the OpenCms plugin for IntelliJ, please
 * see the project website at GitHub:
 * https://github.com/mediaworx/opencms-intellijplugin
 *
 * Copyright (C) 2007-2016 mediaworx berlin AG (http://www.mediaworx.com)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package com.mediaworx.intellij.opencmsplugin.sync;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Messages;
import com.mediaworx.intellij.opencmsplugin.entities.SyncEntity;
import com.mediaworx.intellij.opencmsplugin.exceptions.CmsConnectionException;
import com.mediaworx.intellij.opencmsplugin.exceptions.CmsPermissionDeniedException;
import com.mediaworx.intellij.opencmsplugin.exceptions.CmsPushException;
import com.mediaworx.intellij.opencmsplugin.tools.PluginTools;
import org.apache.chemistry.opencmis.client.api.*;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.RepositoryCapabilities;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.*;
import org.apache.commons.io.FileUtils;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Adapter used to sync the RFS with the OpenCms VFS. Doesn't handle properties, siblings or content types.
 * @author Kai Widmann, widmann@mediaworx.com
 */
public class VfsAdapter {

    private static final Logger LOG = Logger.getInstance(VfsAdapter.class);

    /** the CMIS session */
    private Session session;

    /** boolean flag denoting if the adapter is connected */
    private boolean connected;

    /** repository URL, for OpenCms CMIS usually "http://localhost:8080/opencms/cmisatom/cmis-offline/" */
    private String atompubUrl;

    /** OpenCms user with sufficient privileges to read/write from/to the VFS, e.g. "Admin" */
    private String user;

    /** the OpenCms user's password */
    private String password;

    /**
     * creates a new VfsAdapter that may be connected by calling {@link #startSession()}
     * @param atompubUrl repository URL, for OpenCms CMIS usually "http://localhost:8080/opencms/cmisatom/cmis-offline/"
     * @param user       OpenCms user with sufficient privileges to read/write from/to the VFS, e.g. "Admin"
     * @param password   the OpenCms user's password
     */
    public VfsAdapter(String atompubUrl, String user, String password) {

        if (atompubUrl == null || atompubUrl.length() == 0) {
            throw new IllegalArgumentException("parameter atompubUrl must not be null or empty");
        }
        if (user == null || user.length() == 0) {
            throw new IllegalArgumentException("parameter user must not be null or empty");
        }
        if (password == null || password.length() == 0) {
            throw new IllegalArgumentException("parameter password must not be null or empty");
        }

        this.atompubUrl = atompubUrl;
        this.user = user;
        this.password = password;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * starts the CMIS session that is used to push or pull files/folders
     */
    public void startSession() throws CmsConnectionException {

        if (password != null && password.length() > 0) {

            Map<String, String> sessionParams = new HashMap<String, String>();

            // Create a SessionFactory and set up the SessionParameter map
            SessionFactory sessionFactory = SessionFactoryImpl.newInstance();

            // user credentials
            sessionParams.put(SessionParameter.USER, user);
            sessionParams.put(SessionParameter.PASSWORD, password);

            // repository
            sessionParams.put(SessionParameter.ATOMPUB_URL, atompubUrl);
            sessionParams.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());

            try {
                // find all the repositories at this URL - there should only be one.
                List<Repository> repositories = sessionFactory.getRepositories(sessionParams);
                for (Repository r : repositories) {
                    LOG.info("Found repository: " + r.getName());
                }

                // create session with the first (and only) repository
                Repository repository = repositories.get(0);
                sessionParams.put(SessionParameter.REPOSITORY_ID, repository.getId());
                sessionParams.put(SessionParameter.CONNECT_TIMEOUT, "500");

                LOG.info("Starting CMIS session using repository " + atompubUrl);
                this.session = sessionFactory.createSession(sessionParams);

                if (this.session != null) {
                    connected = true;
                } else {
                    connected = false;
                    LOG.info("Error: CMIS session is null");
                }
            } catch (Exception e) {
                LOG.info("Exception connecting to VFS", e);
                connected = false;
                throw new CmsConnectionException("Connection to OpenCms VFS failed. Is OpenCms running?");
            }
        }
    }

    /**
     * checks if a VFS resource exists at the given path
     * @param path  the path to be checked (full root path, e.g.
     *              <code>/system/modules/com.mycompany.mymodule/classes/messages.properties</code>)
     * @return  <code>true</code> if the resource exists in the VFS, <code>false</code> otherwise
     */
    public boolean exists(String path) {
        if (!connected) {
            LOG.warn("not connected");
            return false;
        }
        if (!path.startsWith("/")) {
            return false;
        }
        try {
            session.getObjectByPath(path);
            return true;
        } catch (CmisObjectNotFoundException e) {
            return false;
        }
    }

    /**
     * retrieves (pulls) the VFS resource at the given path
     * @param path  path of the resource to be pulled
     * @return  the VFS resource
     * @throws CmsPermissionDeniedException
     */
    public CmisObject getVfsObject(String path) throws CmsPermissionDeniedException {
        if (!connected) {
            LOG.warn("not connected");
            return null;
        }
        path = PluginTools.ensureUnixPath(path);
        try {
            return session.getObjectByPath(path);
        } catch (CmisObjectNotFoundException e) {
            return null;
        } catch (CmisPermissionDeniedException e) {
            LOG.warn("Permission denied, can't access " + path, e);
            throw new CmsPermissionDeniedException("Permission denied, can't access " + path, e);
        } catch (CmisConnectionException e) {
            Messages.showDialog("Error connecting to the VFS" + e.getMessage() + "\nIs OpenCms running?", "Error",
                    new String[] { "Ok" }, 0, Messages.getErrorIcon());
            LOG.warn("Error connecting to the VFS", e);
            connected = false;
            return null;
        }
    }

    /**
     * retrieves a VFS folder, creating it if it doesn't exist
     * @param path  the path of the folder to be retrieved
     * @return  the VFS folder (may be newly created)
     */
    private Folder getOrCreateFolder(String path) {
        if (!connected) {
            LOG.warn("not connected");
            return null;
        }

        // check if the folder exists
        try {
            return (Folder) session.getObjectByPath(path);
        }
        // if the folder does not exist, create it
        catch (CmisObjectNotFoundException e) {
            String parentPath = path.substring(0, path.lastIndexOf("/"));
            String foldername = path.substring(path.lastIndexOf("/") + 1, path.length());
            LOG.info("creating folder " + path);
            LOG.info("parent path " + parentPath);
            LOG.info("foldername " + foldername);

            Folder parent;

            try {
                parent = (Folder) session.getObjectByPath(parentPath);
            } catch (CmisObjectNotFoundException e2) {
                parent = getOrCreateFolder(parentPath);
            }

            Map<String, String> newFolderProps = new HashMap<String, String>();
            newFolderProps.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value());
            newFolderProps.put(PropertyIds.NAME, foldername);
            return parent.createFolder(newFolderProps);
        }
    }

    /**
     * creates a folder in the VFS and returns it. If the folder already exists, the existing folder is returned
     * @param path  the folder's VFS path (full root path, e.g.
     *              <code>/system/modules/com.mycompany.mymodule/classes</code>)
     * @return  the newly created folder (or the folder that existed previously)
     */
    public Folder createFolder(String path) {
        return getOrCreateFolder(path);
    }

    /**
     * pushes a file from the RFS to the VFS
     * @param entity    the sync entity representing the file to be pushed
     * @return  a CMIS document of the newly created VFS file
     * @throws CmsPushException
     */
    public Document pushFile(SyncEntity entity) throws CmsPushException {
        if (!connected) {
            LOG.info("not connected");
            return null;
        }

        File rfsFile = entity.getFile();
        FileInputStream rfsFileInputStream = null;
        Document vfsFile = null;
        long vfsFileModifiedTime = 0;

        try {
            rfsFileInputStream = new FileInputStream(rfsFile);
            String mimetype = new MimetypesFileTypeMap().getContentType(rfsFile);

            ContentStream contentStream = session.getObjectFactory().createContentStream(rfsFile.getName(),
                    rfsFile.length(), mimetype, rfsFileInputStream);

            // if the file already exists in the VFS ...
            if (entity.replaceExistingEntity()) {
                // ... update its content
                vfsFile = (Document) entity.getVfsObject();
                vfsFile.setContentStream(contentStream, true, true);
            }
            // if the file doesn't exist in the VFS
            else {
                // ... get the parent folder object from the VFS
                String parentPath = entity.getVfsPath().substring(0, entity.getVfsPath().lastIndexOf("/"));
                Folder parent = getOrCreateFolder(parentPath);

                // ... and create the file as Document Object under the parent folder
                Map<String, Object> properties = new HashMap<String, Object>();
                properties.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value());
                properties.put(PropertyIds.NAME, rfsFile.getName());
                vfsFile = parent.createDocument(properties, contentStream, VersioningState.NONE);
            }

            // Set file modification date in the VFS to the RFS file date
            // This does not work when using OpenCms since OpenCms always sets the date of the CMIS change event
            // That's why the file date in the RFS has to be set to the VFS date (see finally block)
            /*
            Map<String, Object> dateProperties = new HashMap<String, Object>(1);
            GregorianCalendar modifiedGC = new GregorianCalendar();
            modifiedGC.setTime(new Date(rfsFile.lastModified()));
            dateProperties.put(PropertyIds.LAST_MODIFICATION_DATE, modifiedGC);
            vfsFile.updateProperties(dateProperties, false);
            */

            vfsFileModifiedTime = vfsFile.getLastModificationDate().getTimeInMillis();
        } catch (FileNotFoundException e) {
            LOG.info("File not found.");
        } catch (CmisNameConstraintViolationException e) {
            throw new CmsPushException("Could not push entity " + entity.getVfsPath()
                    + ", there was a problem with the resource name.\n" + e.getMessage(), e);
        } catch (CmisRuntimeException e) {
            throw new CmsPushException("Could not push entity " + entity.getVfsPath()
                    + ", there may be an issue with a lock or an XML validation issue. Look at the OpenCms log file to find out what went wrong.\n"
                    + e.getMessage(), e);
        } finally {
            try {
                if (rfsFileInputStream != null) {
                    rfsFileInputStream.close();
                }

                if (vfsFileModifiedTime > 0) {
                    // Since setting the modification Date on the VFS file ain't possible, set the date for the RFS file
                    if (rfsFile.setLastModified(vfsFileModifiedTime)) {
                        LOG.info("Setting lastModificationDate successful");
                    } else {
                        LOG.info("Setting lastModificationDate NOT successful");
                    }
                }
            } catch (IOException e) {
                // do nothing
            }
        }

        return vfsFile;
    }

    /**
     * pulls a VFS file to the RFS
     * @param syncEntity    the sync entity representing the file to be pulled
     */
    public void pullFile(SyncEntity syncEntity) {
        if (!connected) {
            LOG.info("not connected");
            return;
        }
        Document document = (Document) syncEntity.getVfsObject();

        LOG.info("Pulling " + syncEntity.getVfsPath() + " to " + syncEntity.getOcmsModule().getLocalVfsRoot());

        InputStream is = document.getContentStream().getStream();
        File rfsFile = createRealFile(syncEntity);
        OutputStream os = null;
        try {
            os = new FileOutputStream(rfsFile);
            byte[] buffer = new byte[4096];
            for (int n; (n = is.read(buffer)) != -1;) {
                os.write(buffer, 0, n);
            }
        } catch (IOException e) {
            LOG.info("There was an Exception writing to the local file " + syncEntity.getRfsPath() + ": " + e + "\n"
                    + e.getMessage());
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                // Do nothing
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // Do nothing
                }
            }
            if (!rfsFile.setLastModified(document.getLastModificationDate().getTimeInMillis())) {
                LOG.info("there was an error setting the modification date for " + syncEntity.getRfsPath());
            }
        }
    }

    /**
     * creates a local file (or folder) for this sync entitiy, if none exists
     * @param syncEntity the SyncEntity for which the real file/folder is to be created
     * @return the file handle for the newly created file/folder (or the file/folder that already existed)
     */
    public File createRealFile(SyncEntity syncEntity) {
        File realFile = new File(syncEntity.getRfsPath());
        if (!realFile.exists()) {
            try {
                if (syncEntity.isFolder()) {
                    if (!realFile.mkdirs()) {
                        LOG.warn("The directories for " + syncEntity.getRfsPath() + " could not be created");
                    }
                } else {
                    File parentFolder = realFile.getParentFile();
                    if (!parentFolder.exists()) {
                        FileUtils.forceMkdir(parentFolder);
                    }
                    if (!realFile.createNewFile()) {
                        LOG.warn("The file " + syncEntity.getRfsPath() + " could not be created");
                    }
                }
            } catch (IOException e) {
                LOG.warn("There was an Exception creating the local file " + syncEntity.getRfsPath(), e);
            }
        }
        syncEntity.setFile(realFile);
        return realFile;
    }

    /**
     * deletes a file or folder from the VFS
     * @param vfsPath   the path of the resource to be deleted (full root path, e.g.
     *              <code>/system/modules/com.mycompany.mymodule/formatters/delete_me.jsp</code>)
     */
    public boolean deleteResource(String vfsPath) {
        if (!connected) {
            LOG.warn("not connected");
            return false;
        }
        boolean success = false;
        CmisObject vfsFile = null;
        try {
            vfsFile = getVfsObject(vfsPath);
        } catch (CmsPermissionDeniedException e) {
            LOG.warn("Can't delete " + vfsPath + ", permission denied", e);
        }
        if (vfsFile != null) {
            // Folders
            if (vfsFile instanceof Folder) {
                LOG.info("Deleting the following folder from the VFS: " + vfsPath);
                List<String> failedResourcePaths = ((Folder) vfsFile).deleteTree(true, UnfileObject.DELETE, true);
                success = failedResourcePaths == null || failedResourcePaths.size() <= 0;
            }
            // Files
            else {
                LOG.info("Deleting the following file from the VFS: " + vfsPath);
                vfsFile.delete();
                success = true;
            }
        }
        return success;
    }

    /**
     * checks if the adapter is connected (a CMIS session is active)
     * @return  <code>true</code> id the adapter is connected, <code>false</code> otherwise
     */
    public boolean isConnected() {
        if (!connected) {
            return false;
        }
        if (session == null) {
            return false;
        }
        Folder folder = null;
        try {
            folder = session.getRootFolder();
        } catch (CmisConnectionException e) {
            LOG.info("Can't read CMIS repository root folder, not connected", e);
            connected = false;
        } catch (CmisObjectNotFoundException e) {
            LOG.info("Can't read CMIS repository root folder, not connected", e);
            connected = false;
        } catch (CmisPermissionDeniedException e) {
            LOG.info("CMIS says permission denied, not connected", e);
            connected = false;
        }
        return folder != null && connected;
    }

    /**
     * clears the CMIS session cache
     */
    public void clearCache() {
        session.clear();
    }

    /**
     * logs the CMIS capabilities, for debugging purposes
     */
    public void logCapabilities() {
        LOG.info("Printing repository capabilities...");
        final RepositoryInfo repInfo = session.getRepositoryInfo();
        RepositoryCapabilities cap = repInfo.getCapabilities();
        LOG.info("\nNavigation Capabilities");
        LOG.info("-----------------------");
        LOG.info("Get descendants supported: " + (cap.isGetDescendantsSupported() ? "true" : "false"));
        LOG.info("Get folder tree supported: " + (cap.isGetFolderTreeSupported() ? "true" : "false"));
        LOG.info("\nObject Capabilities");
        LOG.info("-----------------------");
        LOG.info("Content Stream: " + cap.getContentStreamUpdatesCapability().value());
        LOG.info("Changes: " + cap.getChangesCapability().value());
        LOG.info("Renditions: " + cap.getRenditionsCapability().value());
        LOG.info("\nFiling Capabilities");
        LOG.info("-----------------------");
        LOG.info("Multifiling supported: " + (cap.isMultifilingSupported() ? "true" : "false"));
        LOG.info("Unfiling supported: " + (cap.isUnfilingSupported() ? "true" : "false"));
        LOG.info("Version specific filing supported: "
                + (cap.isVersionSpecificFilingSupported() ? "true" : "false"));
        LOG.info("\nVersioning Capabilities");
        LOG.info("-----------------------");
        LOG.info("PWC searchable: " + (cap.isPwcSearchableSupported() ? "true" : "false"));
        LOG.info("PWC updatable: " + (cap.isPwcUpdatableSupported() ? "true" : "false"));
        LOG.info("All versions searchable: " + (cap.isAllVersionsSearchableSupported() ? "true" : "false"));
        LOG.info("\nQuery Capabilities");
        LOG.info("-----------------------");
        LOG.info("Query: " + cap.getQueryCapability().value());
        LOG.info("Join: " + cap.getJoinCapability().value());
        LOG.info("\nACL Capabilities");
        LOG.info("-----------------------");
        LOG.info("ACL: " + cap.getAclCapability().value());
        LOG.info("End of  repository capabilities");
    }

    /**
     * logs the properties of a CMIS object, for debugging purposes
     */
    public void logCmisObjectProperties(CmisObject object) {
        List<Property<?>> properties = object.getProperties();
        for (Property<?> property : properties) {
            LOG.info("Property: " + property.getDisplayName() + " (" + property.getLocalName() + ") - "
                    + property.getValue().toString());
        }
    }
}