Java tutorial
/* * 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.intellij.openapi.vfs.LocalFileSystem; import com.mediaworx.intellij.opencmsplugin.OpenCmsPlugin; import com.mediaworx.intellij.opencmsplugin.configuration.OpenCmsPluginConfigurationData; import com.mediaworx.intellij.opencmsplugin.connector.AutoPublishMode; import com.mediaworx.intellij.opencmsplugin.entities.ExportEntity; import com.mediaworx.intellij.opencmsplugin.entities.SyncEntity; import com.mediaworx.intellij.opencmsplugin.entities.SyncFolder; import com.mediaworx.intellij.opencmsplugin.exceptions.CmsPushException; import com.mediaworx.intellij.opencmsplugin.exceptions.OpenCmsConnectorException; import com.mediaworx.intellij.opencmsplugin.opencms.OpenCmsModule; import com.mediaworx.intellij.opencmsplugin.opencms.OpenCmsModuleExportPoint; import com.mediaworx.intellij.opencmsplugin.opencms.OpenCmsModuleResource; import com.mediaworx.intellij.opencmsplugin.tools.PluginTools; import com.mediaworx.intellij.opencmsplugin.toolwindow.OpenCmsToolWindowConsole; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.*; import java.util.regex.Matcher; /** * Syncs OpenCms and the local file system, is run as a separate thread */ public class SyncJob implements Runnable { private static final Logger LOG = Logger.getInstance(SyncJob.class); public static final String ERROR_PREFIX = "ERROR: "; private static final String CLASSES_PATH = "WEB-INF/classes"; private static final String IDE_CONNECTOR_PACKAGE = "com.mediaworx.opencms.ideconnector"; private static final Set<String> IDE_CONNECTOR_PARENT_PATHS = new HashSet<String>(); static { String[] ideConnectorPathFolders = IDE_CONNECTOR_PACKAGE.split("\\."); StringBuilder ideConnectorParentPath = new StringBuilder(CLASSES_PATH); IDE_CONNECTOR_PARENT_PATHS.add(ideConnectorParentPath.toString()); for (String ideConnectorPathFolder : ideConnectorPathFolders) { ideConnectorParentPath.append("/").append(ideConnectorPathFolder); IDE_CONNECTOR_PARENT_PATHS.add(ideConnectorParentPath.toString()); } } private OpenCmsPlugin plugin; private OpenCmsToolWindowConsole console; private OpenCmsPluginConfigurationData config; private VfsAdapter adapter; private SyncList syncList; private boolean pullMetadataOnly; private List<SyncEntity> refreshEntityList; private List<ExportEntity> exportList; private List<String> publishList; private boolean publish; /** * Creates a new SyncJob for the given SyncList * @param plugin the current plugin instance * @param syncList list of SyncEntities to be synced by this SyncJob */ public SyncJob(OpenCmsPlugin plugin, SyncList syncList) { this.plugin = plugin; config = plugin.getPluginConfiguration(); publish = config.isPluginConnectorEnabled() && config.getAutoPublishMode() == AutoPublishMode.ALL; adapter = plugin.getVfsAdapter(); this.refreshEntityList = new ArrayList<SyncEntity>(); this.exportList = new ArrayList<ExportEntity>(); this.publishList = new ArrayList<String>(); setSyncList(syncList); } /** * Starts the SyncJob (done as a separate thread) */ public void run() { console = plugin.getConsole(); int step = 1; int numSteps = 1; if (config.isPluginConnectorEnabled() && config.isPullMetadataEnabled()) { numSteps += 1; } if (syncList.isSyncModuleMetaData()) { numSteps += 2; } if (!pullMetadataOnly && publish) { numSteps += 1; } if (!pullMetadataOnly && numExportEntities() > 0) { numSteps += 1; } // ######## SYNC FILES / FOLDERS ################################ if (!pullMetadataOnly) { console.info("Step " + (step++) + "/" + numSteps + ": Syncing files and folders"); for (SyncEntity entity : syncList) { doSync(entity); } console.info("---- Sync finished ----\n"); } // ######## OR CLEAN UP META DATA FOLDERS ################################ else { console.info("Step " + (step++) + "/" + numSteps + ": Clean meta data folders for affected modules"); for (OpenCmsModule ocmsModule : syncList.getOcmsModules()) { console.info("Cleaning meta data folder for " + ocmsModule.getModuleName()); cleanupModuleMetaFolder(ocmsModule); } } if (syncList.isSyncModuleMetaData()) { // ######## PULL MODULE MANIFESTS ################################ console.info("Step " + (step++) + "/" + numSteps + ": Pull module manifests"); pullModuleManifests(); console.info("---- Module manifest pull finished ----\n"); // ######## PULL META INFOS FOR MODULE RESOURCE PARENTS ################################ console.info("Step " + (step++) + "/" + numSteps + ": Pulling resource meta data for module resource path ancestors from OpenCms"); pullModuleResourcePathAncestorMetaInfos(); console.info("---- Pull of meta data for module resource path ancestors finished ----\n"); } // ######## PULL RESOURCE VFS META INFORMATION ################################ if (config.isPluginConnectorEnabled() && config.isPullMetadataEnabled()) { console.info("Step " + (step++) + "/" + numSteps + ": Pulling resource meta data from OpenCms"); pullResourceMetaInfos(); console.info("---- Resource meta info pull finished ----\n"); } if (!pullMetadataOnly) { // ######## PUBLISHING ########################################### if (publish) { console.info("Step " + step + "/" + numSteps + ": Publishing"); if (publishList.size() > 0) { try { plugin.getPluginConnector().publishResources(publishList, false); console.info("A direct publish session was started successfully"); } catch (OpenCmsConnectorException e) { console.error(e.getMessage()); } catch (IOException e) { LOG.warn("There was an exception while publishing resources after sync", e); console.error( "There was an exception while publishing resources after syncing. Is OpenCms running? Please have a look at the OpenCms log file and/or the IntelliJ log file."); } } else { console.info("No resources need publishing"); } console.info("---- Publish finished ----\n"); } // ######## EXPORT POINT HANDLING ################################ if (numExportEntities() > 0) { console.info("Step " + step + "/" + numSteps + ": Handling export points"); for (ExportEntity entity : exportList) { doExportPointHandling(entity); } console.info("---- Copying of ExportPoints finished ----\n"); } } // ######## REFRESH IDEA FILESYSTEM ############################## if (hasRefreshEntities()) { List<SyncEntity> pullEntityList = getRefreshEntityList(); List<File> refreshFiles = new ArrayList<File>(pullEntityList.size()); for (SyncEntity entity : pullEntityList) { refreshFiles.add(entity.getFile()); } try { LocalFileSystem.getInstance().refreshIoFiles(refreshFiles); } catch (Exception e) { // if there's an exception then the file was not found. } } console.info("#### SYNC FINISHED ####"); } private void setSyncList(SyncList syncList) { this.syncList = syncList; this.pullMetadataOnly = syncList.isPullMetaDataOnly(); if (!syncList.isPullMetaDataOnly()) { for (SyncEntity entity : syncList) { if (entity.getSyncAction() == SyncAction.PULL || entity.getSyncAction() == SyncAction.DELETE_RFS) { this.refreshEntityList.add(entity); } addSyncEntityToExportListIfNecessary(entity); } } } /** * @return the List of SyncEntities handled by this SyncJob */ public SyncList getSyncList() { return syncList; } private void addSyncEntityToExportListIfNecessary(SyncEntity syncEntity) { // if publishing is enabled, pushed and deleted entities don't have to be handled (this is done by OpenCms) if (publish && (syncEntity.getSyncAction() == SyncAction.PUSH || syncEntity.getSyncAction() == SyncAction.DELETE_VFS)) { return; } List<OpenCmsModuleExportPoint> exportPoints = syncEntity.getOcmsModule().getExportPoints(); if (exportPoints != null) { String localModuleVfsRoot = syncEntity.getOcmsModule().getLocalVfsRoot(); String entityVfsPath = syncEntity.getVfsPath(); for (OpenCmsModuleExportPoint exportPoint : exportPoints) { String vfsSource = exportPoint.getVfsSource(); if (entityVfsPath.startsWith(vfsSource)) { String destination = exportPoint.getRfsTarget(); String relativePath = entityVfsPath.substring(vfsSource.length()); ExportEntity exportEntity = new ExportEntity(); exportEntity.setSourcePath(localModuleVfsRoot + entityVfsPath); exportEntity.setTargetPath(config.getWebappRoot() + "/" + destination + relativePath); exportEntity.setVfsPath(entityVfsPath); exportEntity.setDestination(destination); exportEntity.setToBeDeleted(syncEntity.getSyncAction().isDeleteAction()); addExportEntity(exportEntity); } } } } private void addExportEntity(ExportEntity entity) { exportList.add(entity); } private List<SyncEntity> getRefreshEntityList() { return refreshEntityList; } private int getNumRefreshEntities() { return refreshEntityList.size(); } private boolean hasRefreshEntities() { return getNumRefreshEntities() > 0; } private int numExportEntities() { return exportList.size(); } private void doSync(SyncEntity entity) { if (entity.getSyncAction() == SyncAction.PUSH) { doPush(entity); if (publish) { publishList.add(entity.getVfsPath()); } } else if (entity.getSyncAction() == SyncAction.PULL) { doPull(entity); } else if (entity.getSyncAction() == SyncAction.DELETE_RFS) { doDeleteFromRfs(entity); } else if (entity.getSyncAction() == SyncAction.DELETE_VFS) { doDeleteFromVfs(entity); if (publish) { publishList.add(entity.getVfsPath()); } } } private void doPush(SyncEntity entity) { boolean success = false; String errormessage = null; if (entity.isFolder()) { try { adapter.createFolder(entity.getVfsPath()); success = true; } catch (Exception e) { errormessage = "Error pushing Folder " + entity.getVfsPath() + "\n" + e.getMessage(); } } else if (entity.isFile()) { try { adapter.pushFile(entity); success = true; } catch (CmsPushException e) { errormessage = e.getMessage(); } } if (success) { StringBuilder confirmation = new StringBuilder(); confirmation.append("PUSH: ").append(entity.getVfsPath()).append(" pushed to VFS"); if (entity.replaceExistingEntity()) { confirmation.append(" replacing an existing entity"); } console.info(confirmation.toString()); } else { console.error("PUSH FAILED! " + errormessage); } } private void doPull(SyncEntity entity) { StringBuilder confirmation = new StringBuilder(); if (entity.isFolder()) { try { FileUtils.forceMkdir(new File(entity.getRfsPath())); } catch (IOException e) { console.error("ERROR: couldn't create local directory " + entity.getRfsPath()); LOG.warn("There was an Exception creating a local directory", e); } } else { adapter.pullFile(entity); } confirmation.append("PULL: ").append(entity.getVfsPath()).append(" pulled from VFS to ") .append(entity.getOcmsModule().getLocalVfsRoot()); if (entity.replaceExistingEntity()) { confirmation.append(" replacing an existing entity"); } console.info(confirmation.toString()); } private void doDeleteFromRfs(SyncEntity entity) { StringBuilder confirmation = new StringBuilder("DELETE ").append(entity.getVfsPath()).append(" from ") .append(entity.getOcmsModule().getLocalVfsRoot()).append(" (not in the VFS) - "); File rfsFile = entity.getFile(); if (FileUtils.deleteQuietly(rfsFile)) { confirmation.append(" SUCCESS"); console.info(confirmation.toString()); } else { confirmation.insert(0, "ERROR: "); confirmation.append(" FAILED!"); console.error(confirmation.toString()); } } private void doDeleteFromVfs(SyncEntity entity) { StringBuilder confirmation = new StringBuilder("DELETE ").append(entity.getVfsPath()) .append(" (not in the RFS) - "); if (adapter.deleteResource(entity.getVfsPath())) { confirmation.append(" SUCCESS"); console.info(confirmation.toString()); } else { confirmation.insert(0, "ERROR: "); confirmation.append(" FAILED!"); console.error(confirmation.toString()); } } private void pullResourceMetaInfos() { // build lists with all resources for which meta information is to be pulled / deleted ArrayList<String> pullEntityList = new ArrayList<String>(); for (SyncEntity entity : syncList) { if (!entity.getSyncAction().isDeleteAction()) { pullEntityList.add(entity.getVfsPath()); } } try { HashMap<String, String> metaInfos = plugin.getPluginConnector().getResourceInfos(pullEntityList); for (SyncEntity entity : syncList) { doMetaInfoHandling(console, metaInfos, entity); } } catch (OpenCmsConnectorException e) { console.error(e.getMessage()); } catch (IOException e) { console.error("There was an error retrieving resource meta infos from OpenCms"); LOG.warn("IOException while trying to retrieve meta infos", e); } } private void doMetaInfoHandling(OpenCmsToolWindowConsole console, Map<String, String> metaInfos, SyncEntity entity) { String metaInfoFilePath = entity.getMetaInfoFilePath(); File metaInfoFile = new File(metaInfoFilePath); if (entity.getSyncAction().isDeleteAction()) { FileUtils.deleteQuietly(metaInfoFile); console.info("DELETE: " + metaInfoFilePath); return; } if (metaInfos.containsKey(entity.getVfsPath())) { if (entity instanceof SyncFolder) { String metaFolderPath = ((SyncFolder) entity).getMetaInfoFolderPath(); File metaFolder = new File(metaFolderPath); if (!metaFolder.exists()) { try { FileUtils.forceMkdir(metaFolder); } catch (IOException e) { String message = "ERROR: cant create meta info directory " + metaFolderPath; console.error(message); LOG.warn(message, e); return; } } } try { String metaInfoStr = PluginTools.ensureUnixNewline(metaInfos.get(entity.getVfsPath())) + "\n"; FileUtils.writeStringToFile(metaInfoFile, metaInfoStr, "UTF-8"); } catch (IOException e) { String message = "ERROR: cant create meta info file " + metaInfoFilePath; console.error(message); LOG.warn(message, e); return; } } else { String message = entity.getVfsPath() + " not found in meta info map."; console.error(message); return; } console.info("PULL: Meta info file pulled: " + metaInfoFilePath); } private void pullModuleResourcePathAncestorMetaInfos() { List<OpenCmsModuleResource> resourcePathParents = new ArrayList<OpenCmsModuleResource>(); for (OpenCmsModule ocmsModule : syncList.getOcmsModules()) { Set<String> handledParents = new HashSet<String>(); for (String resourcePath : ocmsModule.getModuleResources()) { addParentFolderToResourcePaths(resourcePath, ocmsModule, resourcePathParents, handledParents); } } if (resourcePathParents.size() > 0) { try { Map<String, String> resourceInfos = plugin.getPluginConnector() .getModuleResourceInfos(resourcePathParents); for (OpenCmsModuleResource resourceParent : resourcePathParents) { SyncFolder syncFolder = new SyncFolder(resourceParent.getOpenCmsModule(), resourceParent.getResourcePath(), null, null, SyncAction.PULL, false); doMetaInfoHandling(console, resourceInfos, syncFolder); } } catch (OpenCmsConnectorException e) { console.error(e.getMessage()); } catch (IOException e) { Messages.showDialog( "There was an error pulling the meta information for module resource ancestor folders from OpenCms.\nIs the connector module installed?", "Error", new String[] { "Ok" }, 0, Messages.getErrorIcon()); LOG.warn("There was an Exception pulling the meta information for module resource ancestor folders", e); } } } private void addParentFolderToResourcePaths(String resourcePath, OpenCmsModule ocmsModule, List<OpenCmsModuleResource> resourcePathParents, Set<String> handledParents) { if (resourcePath.endsWith("/")) { resourcePath = resourcePath.substring(0, resourcePath.length() - 1); } if (resourcePath.lastIndexOf("/") > 0) { // > 0 is right because the "/" at position 0 has to be ignored String parentPath = resourcePath.substring(0, resourcePath.lastIndexOf("/")); if (!handledParents.contains(parentPath)) { resourcePathParents.add(new OpenCmsModuleResource(ocmsModule, parentPath)); handledParents.add(parentPath); if (parentPath.contains("/")) { addParentFolderToResourcePaths(parentPath, ocmsModule, resourcePathParents, handledParents); } } } } private void pullModuleManifests() { // collect the module names in a List List<String> moduleNames = new ArrayList<String>(syncList.getOcmsModules().size()); for (OpenCmsModule ocmsModule : syncList.getOcmsModules()) { moduleNames.add(ocmsModule.getModuleName()); } if (moduleNames.size() > 0) { try { // pull the module manifests Map<String, String> manifestInfos = plugin.getPluginConnector().getModuleManifests(moduleNames); for (OpenCmsModule ocmsModule : syncList.getOcmsModules()) { if (manifestInfos.containsKey(ocmsModule.getModuleName())) { // put the manifest to a file String manifestPath = ocmsModule.getManifestRoot() + "/manifest_stub.xml"; String manifest = manifestInfos.get(ocmsModule.getModuleName()); if (ocmsModule.isSetSpecificModuleVersionEnabled() && StringUtils.isNotEmpty(ocmsModule.getModuleVersion())) { manifest = manifest.replaceFirst("<version>[^<]*</version>", "<version>" + Matcher.quoteReplacement(ocmsModule.getModuleVersion()) + " </version>"); } manifest = PluginTools.ensureUnixNewline(manifest) + "\n"; FileUtils.writeStringToFile(new File(manifestPath), manifest, Charset.forName("UTF-8")); console.info("PULL: " + manifestPath + " pulled from OpenCms"); } else { LOG.warn("No manifest found for module " + ocmsModule.getModuleName()); } } } catch (OpenCmsConnectorException e) { console.error(e.getMessage()); } catch (IOException e) { Messages.showDialog( "There was an error pulling the module manifest files from OpenCms.\nIs the connector module installed?", "Error", new String[] { "Ok" }, 0, Messages.getErrorIcon()); LOG.warn("There was an Exception pulling the module manifests", e); } } } private static boolean isParentOfIdeConnectorPath(String path) { for (String ideConnectorParentPath : IDE_CONNECTOR_PARENT_PATHS) { if (path.endsWith(ideConnectorParentPath)) { return true; } } return false; } private void doExportPointHandling(ExportEntity entity) { StringBuilder confirmation = new StringBuilder(); StringBuilder notice = new StringBuilder(); if (!entity.isToBeDeleted()) { confirmation.append("Copy of ").append(entity.getVfsPath()).append(" to ") .append(entity.getDestination()).append(" - "); File file = new File(entity.getSourcePath()); if (file.exists()) { if (file.isFile()) { try { FileUtils.copyFile(file, new File(entity.getTargetPath())); confirmation.append("SUCCESS"); } catch (IOException e) { confirmation.insert(0, ERROR_PREFIX); confirmation.append("FAILED (").append(e.getMessage()).append(")"); } } else if (file.isDirectory()) { try { FileUtils.copyDirectory(file, new File(entity.getTargetPath())); confirmation.append("SUCCESS"); } catch (IOException e) { confirmation.insert(0, ERROR_PREFIX); confirmation.append("FAILED (").append(e.getMessage()).append(")"); } } } else { confirmation.append(" - FILE NOT FOUND"); } } else { String targetPath = entity.getTargetPath(); String vfsPath = entity.getVfsPath(); deleteExportedResource(vfsPath, targetPath, confirmation, notice); } if (confirmation.indexOf(ERROR_PREFIX) > -1) { console.error(confirmation.toString()); } else { console.info(confirmation.toString()); } if (notice.length() > 0) { console.notice(notice.toString()); } } /** * Deletes a resource file that was exported via export point handling * @param vfsPath the resource's VFS path * @param targetPath the export point's target path within the WebApp * @param confirmation StringBuilder the log is added to * @param notice String builder for an additional notice that may be displayed under the log. This is used * for a warning message if the deletion was skipper because a path containing the IDE * connector would be deleted. */ public static void deleteExportedResource(String vfsPath, String targetPath, StringBuilder confirmation, StringBuilder notice) { confirmation.append("Resource ").append(vfsPath).append(" removed, deletion of exported file ") .append(targetPath).append(" - "); // check if the target path is something that shouldn't be deleted if (isParentOfIdeConnectorPath(targetPath)) { confirmation.append("SKIPPED (paths containing the IDE connector must not be deleted)"); notice.append("Deletion of the folder ").append(targetPath) .append(" was skipped. Please delete any unwanted exported sub resources manually."); } else { File file = new File(targetPath); if (file.exists()) { if (FileUtils.deleteQuietly(file)) { confirmation.append("SUCCESS"); } else { confirmation.insert(0, ERROR_PREFIX); confirmation.append("FAILED"); } } else { confirmation.append("NOT NECESSARY (doesn't exist)"); } } } private void cleanupModuleMetaFolder(OpenCmsModule ocmsModule) { if (ocmsModule != null) { File metaFolder = new File(ocmsModule.getManifestRoot()); if (!metaFolder.isDirectory()) { return; } File[] metaFiles = metaFolder.listFiles(); if (metaFiles == null || metaFiles.length == 0) { return; } for (File metaFile : metaFiles) { FileUtils.deleteQuietly(metaFile); } } } }