Java tutorial
/******************************************************************************* * Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved. * * This file is part of the OpenWGA server platform. * * OpenWGA 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. * * In addition, a special exception is granted by the copyright holders * of OpenWGA called "OpenWGA plugin exception". You should have received * a copy of this exception along with OpenWGA in file COPYING. * If not, see <http://www.openwga.com/gpl-plugin-exception>. * * OpenWGA 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 OpenWGA in file COPYING. * If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package de.innovationgate.wgpublisher.design.fs; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemException; import org.apache.commons.vfs2.FileType; import org.apache.commons.vfs2.FileUtil; import org.apache.log4j.Logger; import de.innovationgate.utils.MD5HashingInputStream; import de.innovationgate.utils.MD5HashingOutputStream; import de.innovationgate.utils.NullPlaceHolder; import de.innovationgate.utils.WGUtils; import de.innovationgate.utils.cache.Cache; import de.innovationgate.utils.cache.CacheException; import de.innovationgate.webgate.api.WGAPIException; import de.innovationgate.webgate.api.WGAuthorisationException; import de.innovationgate.webgate.api.WGBackendException; import de.innovationgate.webgate.api.WGCreationException; import de.innovationgate.webgate.api.WGDatabase; import de.innovationgate.webgate.api.WGDesignChangeListener; import de.innovationgate.webgate.api.WGDesignDocument; import de.innovationgate.webgate.api.WGDocument; import de.innovationgate.webgate.api.WGDocumentCore; import de.innovationgate.webgate.api.WGDocumentDoesNotExistException; import de.innovationgate.webgate.api.WGDocumentKey; import de.innovationgate.webgate.api.WGException; import de.innovationgate.webgate.api.WGSessionContext; import de.innovationgate.wga.common.DesignDirectory; import de.innovationgate.wga.common.beans.csconfig.v1.CSConfig; import de.innovationgate.wga.common.beans.csconfig.v1.InvalidCSConfigVersionException; import de.innovationgate.wga.common.beans.csconfig.v1.PluginID; import de.innovationgate.wga.common.beans.csconfig.v1.Version; import de.innovationgate.wga.config.DesignReference; import de.innovationgate.wga.config.WGAConfiguration; import de.innovationgate.wga.model.WGADesignConfigurationModel; import de.innovationgate.wga.server.api.WGA; import de.innovationgate.wgpublisher.WGACore; import de.innovationgate.wgpublisher.design.DesignProviderCoreWrapper; import de.innovationgate.wgpublisher.design.OverlayData; import de.innovationgate.wgpublisher.design.OverlayData.ResourceData; import de.innovationgate.wgpublisher.design.OverlayDesignProvider; import de.innovationgate.wgpublisher.design.OverlayStatus; import de.innovationgate.wgpublisher.design.OverlayStatus.ChangeType; import de.innovationgate.wgpublisher.design.OverlayStatus.ChangedDocument; import de.innovationgate.wgpublisher.design.WGADesignProvider; import de.innovationgate.wgpublisher.design.db.PluginDesignProvider; import de.innovationgate.wgpublisher.design.fs.DesignFileDocument.Data; import de.innovationgate.wgpublisher.design.sync.DesignFileValidator; import de.innovationgate.wgpublisher.design.sync.WGDesignSyncException; public class FileSystemDesignProvider extends FileSystemDesignManager implements WGADesignProvider { private boolean _strictFCDateDetermination; private DesignReference _designReference; private String _variantSuffix; private boolean _notifying = false; protected boolean _noBackgroundChanges = false; public FileSystemDesignProvider(DesignReference ref, WGACore core, WGDatabase db, String path, Map<String, String> options) throws WGDesignSyncException, IOException, WGAPIException, InstantiationException, IllegalAccessException, InvalidCSConfigVersionException { super(core, db, path, options); _designReference = ref; _name = "Design Directory " + getBaseFolder().getURL().toString(); _strictFCDateDetermination = true; _lookupVariants = WGUtils.getBooleanMapValue(options, OPTION_DESIGNVARIANTS, false); _variantSuffix = "." + getDB().getDbReference(); // In case of the base folder being a file (an archived design) we can set notifying to true, as there will be no updates. This enables the WGAPI to cache designs. _notifying = getFsResources().getBaseFolder().getType().equals(FileType.FILE); if (options.containsKey(OPTION_NO_BACKGROUND_CHANGES)) { _noBackgroundChanges = WGUtils.toBoolean(options.get(OPTION_NO_BACKGROUND_CHANGES), false); } else { _noBackgroundChanges = (Boolean) WGA.get(core).server() .getServerOption(WGAConfiguration.SERVEROPTION_CACHE_DESIGN_NO_BACKGROUND_CHANGES); } } private String _name; public static final String OPTION_NO_BACKGROUND_CHANGES = "noBackgroundChanges"; public static final String OPTION_SYNC = "sync"; public boolean providesType(int type) { return _syncedDoctypes.contains(new Integer(type)); } public void addDesignChangeListener(WGDesignChangeListener changeListener) { } public WGDocumentCore createDesignDocument(int type, String name, String mediaKey) throws WGAuthorisationException, WGCreationException { try { switch (type) { case WGDocument.TYPE_TML: { FileObject newTml = getTmlFolder() .resolveFile(mediaKey.toLowerCase() + "/" + name.replace(":", "/") + ".tml"); if (newTml.exists()) { throw new WGCreationException( "Document already exists: " + (new WGDocumentKey(type, name, mediaKey)).toString()); } newTml.createFile(); break; } case WGDocument.TYPE_CSSJS: { String suffix = DesignDirectory.getScriptInformation(mediaKey).getSuffix(); FileObject newScript = getScriptTypeFolder(mediaKey) .resolveFile(mediaKey.toLowerCase() + "/" + name.replace(":", "/") + suffix); if (newScript.exists()) { throw new WGCreationException( "Document already exists: " + (new WGDocumentKey(type, name, mediaKey)).toString()); } newScript.createFile(); break; } case WGDocument.TYPE_FILECONTAINER: { FileObject newFC = getFilesFolder().resolveFile(name.replace(":", "/")); if (newFC.exists()) { throw new WGCreationException( "Document already exists: " + (new WGDocumentKey(type, name, mediaKey)).toString()); } newFC.createFolder(); break; } } clearCache(); return getDesignObject(type, name, mediaKey); } catch (Exception e) { throw new WGCreationException( "Exception creating design document " + (new WGDocumentKey(type, name, mediaKey)).toString(), e); } } public void dispose() { close(); } public WGDocumentCore getDesignObject(int type, String name, String strMediaKey) throws WGBackendException { try { WGDocumentKey docKey = new WGDocumentKey(type, name, strMediaKey); // Check the cache if we have disabled background changes if (isNoBackgroundChanges()) { DesignFileDocument.Data cacheData = readDesignFileCache(docKey); if (cacheData != null) { return wrapVariantCore(new DesignFileDocument(this, cacheData)); } } // Find the design file switch (type) { case WGDocument.TYPE_TML: { ModuleFile file = getTMLModuleFile(name, strMediaKey); if (file == null || !file.getFile().exists()) { refreshIfUpdatable(getFsResources().getTmlFolder()); file = getTMLModuleFile(name, strMediaKey); } if (file != null) { return wrapVariantCore(new DesignFileDocument(this, file)); } break; } case WGDocument.TYPE_CSSJS: { ModuleFile file = getScriptModuleFile(name, strMediaKey); if (file == null || !file.getFile().exists()) { refreshIfUpdatable(getFsResources().getScriptFolder()); file = getScriptModuleFile(name, strMediaKey); } if (file != null) { return wrapVariantCore(new DesignFileDocument(this, file)); } break; } case WGDocument.TYPE_FILECONTAINER: { ModuleFile file = getFileContainerFile(name); if (file == null || !file.getFile().exists()) { refreshIfUpdatable(getFsResources().getFilesFolder()); file = getFileContainerFile(name); } if (file != null) { return wrapVariantCore(new DesignFileDocument(this, file)); } break; } } if (isNoBackgroundChanges()) { writeDesignFileCache(docKey, null); } return null; } catch (WGDocumentDoesNotExistException e) { return null; } catch (Exception e) { throw new WGBackendException("Exception retrieving design document '" + name + "' of type " + WGDocument.doctypeNumberToName(type), e); } } protected Data readDesignFileCache(WGDocumentKey docKey) throws WGDocumentDoesNotExistException { Cache cache = getCore().getDesignFileCache(); String cacheKey = createDesignCacheKey(docKey); Data cacheData = null; try { Object cacheObject = cache.read(cacheKey); if (cacheObject instanceof NullPlaceHolder) { throw new WGDocumentDoesNotExistException(); } else { return (Data) cacheObject; } } catch (CacheException e) { getLog().error("Exception checking design cache", e); return null; } } protected void writeDesignFileCache(WGDocumentKey docKey, DesignFileDocument.Data data) { Cache cache = getCore().getDesignFileCache(); String cacheKey = createDesignCacheKey(docKey); try { if (data == null) { cache.write(cacheKey, new NullPlaceHolder(), getDesignReference().toString()); } else { cache.write(cacheKey, data, getDesignReference().toString()); } } catch (CacheException e) { getLog().error("Exception writing design cache", e); } } @Override public void clearCache() throws WGException { try { Cache cache = getCore().getDesignFileCache(); cache.flushGroup(getDesignReference().toString()); } catch (CacheException e) { throw new WGException("Exception clearing design file cache for " + getDesignReference().toString(), e); } } protected String createDesignCacheKey(WGDocumentKey docKey) { StringBuffer cacheKey = new StringBuffer(); // We need to include the reference of the current provider here bc. the dockey may not be qualification enough // (for example when it reflects the path inside a ZIP file) cacheKey.append(getDesignReference()); cacheKey.append("/"); cacheKey.append(docKey.toString()); if (_lookupVariants) { cacheKey.append("//").append(_variantSuffix); } return cacheKey.toString(); } public WGDocumentCore wrapVariantCore(WGDocumentCore core) throws WGAPIException { if (_lookupVariants) { String name = (String) core.getMetaData(WGDesignDocument.META_NAME); if (name.endsWith(_variantSuffix)) { return new DesignProviderCoreWrapper(core, this, true, false); } else { return core; } } else { return core; } } @Override protected ModuleFile getFileContainerFile(String name) throws FileSystemException, WGDesignSyncException { if (_lookupVariants) { ModuleFile file = super.getFileContainerFile(name + _variantSuffix); if (file != null) { return file; } } return super.getFileContainerFile(name); } @Override protected ModuleFile getScriptModuleFile(String name, String codetype) throws FileSystemException, WGDesignSyncException { if (_lookupVariants) { ModuleFile file = super.getScriptModuleFile(name + _variantSuffix, codetype); if (file != null) { return file; } } return super.getScriptModuleFile(name, codetype); } @Override protected ModuleFile getTMLModuleFile(String name, String strMediaKey) throws FileSystemException, WGDesignSyncException { if (_lookupVariants) { ModuleFile file = super.getTMLModuleFile(name + _variantSuffix, strMediaKey); if (file != null) { return file; } } return super.getTMLModuleFile(name, strMediaKey); } public List<WGDocumentCore> getDesignObjects(int type) throws WGBackendException { try { switch (type) { case WGDocument.TYPE_TML: { refreshIfUpdatable(getFsResources().getTmlFolder()); List<ModuleFile> files = getTMLModuleFiles(); return wrapModuleFiles(files); } case WGDocument.TYPE_CSSJS: { refreshIfUpdatable(getFsResources().getScriptFolder()); List<ModuleFile> files = getScriptModuleFiles(); return wrapModuleFiles(files); } case WGDocument.TYPE_FILECONTAINER: { refreshIfUpdatable(getFsResources().getFilesFolder()); List<ModuleFile> files = getFileContainerFiles(); return wrapFileContainerFiles(files); } } return null; } catch (Exception e) { throw new WGBackendException( "Exception retrieving design document list of type " + WGDocument.doctypeNumberToName(type), e); } } private void refreshIfUpdatable(FileObject tmlFolder) throws FileSystemException { if (getFsResources().isUpdateableFileSystem()) { tmlFolder.refresh(); } } public String getName() { return _name; } public boolean isNotifying() { return _notifying; } public boolean isProviderCore(WGDocumentCore core) { return (core instanceof DesignFileDocument); } public void removeDesignChangeListener(WGDesignChangeListener changeListener) { } protected List<WGDocumentCore> wrapFileContainerFiles(List<ModuleFile> files) { List<WGDocumentCore> list = new ArrayList<WGDocumentCore>(); Set<String> addedNames = new HashSet<String>(); for (ModuleFile file : files) { try { WGDocumentCore core = wrapVariantCore(new DesignFileDocument(this, file)); // Filter out base versions of variants if (_lookupVariants) { String coreName = (String) core.getMetaData(WGDesignDocument.META_NAME); // If we try to add a doc that already has been added and is // NO variant, we know that this is the base version which // was // overwritten by a variant. So we skip it. if (addedNames.contains(coreName) && !Boolean.TRUE.equals(core.getMetaData(WGDesignDocument.META_VARIANT))) { continue; } else { addedNames.add(coreName); } } list.add(core); } catch (Exception e) { getLog().error("Exception wrapping file container file as WGAPI document: " + file.getFile().getName().getPath(), e); } } return list; } protected List<WGDocumentCore> wrapModuleFiles(List<ModuleFile> files) { Iterator<ModuleFile> it = files.iterator(); Set<String> addedNames = new HashSet<String>(); List<WGDocumentCore> list = new ArrayList<WGDocumentCore>(); while (it.hasNext()) { ModuleFile moduleFile = (ModuleFile) it.next(); try { WGDocumentCore core = wrapVariantCore(new DesignFileDocument(this, moduleFile)); // Filter out base versions of variants if (_lookupVariants) { String coreName = (String) core.getMetaData(WGDesignDocument.META_NAME); // If we try to add a doc that already has been added and is // NO variant, we know that this is the base version which // was // overwritten by a variant. So we skip it. if (addedNames.contains(coreName) && !Boolean.TRUE.equals(core.getMetaData(WGDesignDocument.META_VARIANT))) { continue; } else { addedNames.add(coreName); } } list.add(core); } catch (Exception e) { getLog().error("Exception wrapping module file as WGAPI document: " + moduleFile.getFile().getName().getPath(), e); } } return list; } protected boolean isStrictFCDateDetermination() { return _strictFCDateDetermination; } public WGDatabase getConsumerDatabase() { return getDB(); } public boolean isLookupVariants() { return _lookupVariants; } public void closeSession() { } public void openSession(WGSessionContext context) { if (_lookupVariants && getFsResources().isUpdateableFileSystem()) { try { getFsResources().refreshAll(); } catch (FileSystemException e) { // We dont want a failure of cache refreshing kill the whole // session, so we log it instead Logger.getLogger("wga.design").error("Exception refreshing file system design cache", e); } } try { updateConfiguration(); } catch (Exception e) { Logger.getLogger("wga.design").error("Exception updating file system design configuration", e); } } public int designHashCode() { return super.designHashCode(); } public DesignReference getDesignReference() { return _designReference; } public static OverlayStatus determineOverlayStatus(FileSystemDesignProvider sourceDesignProvider, PluginID baseId, FileObject targetDirectory, String targetEncoding, Logger log, DesignFileValidator validator) throws Exception { OverlayStatus status = new OverlayStatus(); // Copy an overlay flag file to the system file container FileObject targetFCFolder = targetDirectory.resolveFile(DesignDirectory.FOLDERNAME_FILES); FileObject systemFC = targetFCFolder.resolveFile("system"); if (!systemFC.exists()) { systemFC.createFolder(); } // Import overlay data, if available FileObject overlayDataFile = systemFC.resolveFile(OverlayDesignProvider.OVERLAY_DATA_FILE); if (overlayDataFile.exists()) { try { InputStream in = new BufferedInputStream(overlayDataFile.getContent().getInputStream()); status.setOverlayData(OverlayData.read(in)); in.close(); } catch (Exception e) { log.error("Exception reading overlay status. Creating new status file", e); } if (status.getOverlayData() != null && !status.getOverlayData().getBasepluginName().equals(baseId.getUniqueName())) { throw new WGDesignSyncException("The overlay folder '" + targetDirectory.getName().getPath() + "' is used with plugin '" + status.getOverlayData().getBasepluginName() + "' not '" + baseId.getUniqueName() + "'. Overlay status determination was canceled."); } } Version providerVersion = baseId.getVersion(); if (status.getOverlayData() == null) { OverlayData overlayData = new OverlayData(); overlayData.setBasepluginName(baseId.getUniqueName()); status.setOverlayData(overlayData); status.setNewOverlay(true); overlayData.setInitialBasepluginVersion(providerVersion.toString()); } // Test for version compatibility between base design and overlay status.setCurrentBaseVersion(providerVersion); if (status.getOverlayData().getBasepluginVersion() != null) { Version baseVersion = new Version(status.getOverlayData().getBasepluginVersion()); // Base design version is different than the compliance version of the overlay. Look if it higher (=upgrade) or lower (=error) if (!providerVersion.equals(baseVersion) || providerVersion.getBuildVersion() != baseVersion.getBuildVersion()) { if (providerVersion.compareTo(baseVersion) >= 0 || (providerVersion.equals(baseVersion) && providerVersion.getBuildVersion() > baseVersion.getBuildVersion())) { status.setUpdatedBaseDesign(true); } else if (providerVersion.compareTo(baseVersion) < 0) { throw new WGDesignSyncException("The used base design version (" + providerVersion.toString() + ") is lower than the compliant version for the overlay (" + status.getOverlayData().getBasepluginVersion() + ")."); } } } if (status.isUpdatedBaseDesign()) { log.info("Used version of base design is " + providerVersion.toString() + ". Overlay currently complies with base design version " + status.getOverlayData().getBasepluginVersion() + ". The overlay can be upgraded."); } // Gather changed resources in base design, so we can priorize them against the overlay resources FileObject sourceTmlFolder = sourceDesignProvider.getTmlFolder(); FileObject targetTmlFolder = targetDirectory.resolveFile(DesignDirectory.FOLDERNAME_TML); if (sourceTmlFolder.exists() && sourceTmlFolder.getType().equals(FileType.FOLDER)) { for (FileObject mediaKeyFolder : sourceTmlFolder.getChildren()) { FileObject overlayFolder = mediaKeyFolder.resolveFile(OverlayDesignProvider.OVERLAY_FOLDER); if (overlayFolder.exists()) { FileObject targetMediaKeyFolder = targetTmlFolder .resolveFile(mediaKeyFolder.getName().getBaseName()); determineChangedResources(WGDocument.TYPE_TML, targetMediaKeyFolder, overlayFolder, targetMediaKeyFolder, targetDirectory, sourceDesignProvider.getFileEncoding(), targetEncoding, status, log, validator); } } } FileObject targetScriptFolder = targetDirectory.resolveFile(DesignDirectory.FOLDERNAME_SCRIPT); FileObject sourceScriptFolder = sourceDesignProvider.getScriptFolder(); if (sourceScriptFolder.exists() && sourceScriptFolder.getType().equals(FileType.FOLDER)) { for (FileObject scriptTypeFolder : sourceScriptFolder.getChildren()) { FileObject overlayFolder = scriptTypeFolder.resolveFile(OverlayDesignProvider.OVERLAY_FOLDER); if (overlayFolder.exists()) { FileObject targetScriptTypeFolder = targetScriptFolder .resolveFile(scriptTypeFolder.getName().getBaseName()); determineChangedResources(WGDocument.TYPE_CSSJS, targetScriptTypeFolder, overlayFolder, targetScriptTypeFolder, targetDirectory, sourceDesignProvider.getFileEncoding(), targetEncoding, status, log, validator); } } } FileObject overlayFolder = sourceDesignProvider.getFilesFolder() .resolveFile(OverlayDesignProvider.OVERLAY_FOLDER); if (overlayFolder.exists()) { determineChangedFileContainerResources(targetFCFolder, overlayFolder, targetFCFolder, targetDirectory, null, null, status, log, validator); } return status; } public OverlayStatus determineOverlayStatus(PluginDesignProvider originalDesignProvider) throws Exception { return determineOverlayStatus(originalDesignProvider.getSourceDesignProvider(), originalDesignProvider.getPluginID(), getBaseFolder(), getFileEncoding(), getLog(), _core.getDesignFileValidator()); } public static void createDowngradeFiles(FileSystemDesignProvider originalDesignProvider, OverlayData data, FileObject targetFolder, String targetEncoding, Logger log) throws Exception { for (Map.Entry<String, ResourceData> resourceEntry : data.getOverlayResources().entrySet()) { boolean changed = false; FileObject targetFile = targetFolder.resolveFile(resourceEntry.getKey()); if (targetFile.exists()) { InputStream in = new BufferedInputStream(targetFile.getContent().getInputStream(), 4096); MD5HashingOutputStream out = new MD5HashingOutputStream(new NullOutputStream()); resourceInToOut(in, targetEncoding, out, targetEncoding); if (!out.getHash().equals(resourceEntry.getValue().getMd5Hash())) { changed = true; } } else { changed = true; } if (changed == true) { String basePath = getOverlayResourcePathInBaseDesign(resourceEntry.getKey()); FileObject sourceFile = originalDesignProvider.getBaseFolder().resolveFile(basePath); if (sourceFile.exists()) { FileObject conflictFile = createConflictFile(targetFile); log.info("Writing downgrade file of modified overlay resource " + resourceEntry.getKey() + " to conflict file: " + targetFolder.getName().getRelativeName(conflictFile.getName())); InputStream in = new BufferedInputStream(sourceFile.getContent().getInputStream(), 4096); OutputStream out = new BufferedOutputStream(conflictFile.getContent().getOutputStream(), 4096); resourceInToOut(in, originalDesignProvider.getFileEncoding(), out, targetEncoding); } else { log.warn("Overlay resource '" + resourceEntry.getKey() + "' does not exist any more in the current base design version."); } } } } private static String getOverlayResourcePathInBaseDesign(String resourcePath) { List<String> pathElements = WGUtils.deserializeCollection(resourcePath, "/"); if (pathElements.get(0).equals("files")) { pathElements.add(1, "overlay"); } else { pathElements.add(2, "overlay"); } return WGUtils.serializeCollection(pathElements, "/"); } public static boolean upgradeOverlay(FileSystemDesignProvider originalDesignProvider, PluginID baseId, OverlayStatus status, FileObject targetFolder, String targetEncoding, Logger log, DesignFileValidator validator) throws Exception { if (!status.isUpdatedBaseDesign() && !status.isNewOverlay()) { throw new WGDesignSyncException( "Used base plugin is no higher version than overlay compliance version. Cannot perform upgrade"); } if (status.isNewOverlay()) { log.info("Initializing empty overlay"); } else { log.info("Upgrading overlay from base design version " + status.getCompliantBaseVersion() + " to " + status.getCurrentBaseVersion()); } // Creating new folders (Done separately because there may be empty folders in the overlay which would not be created via resource changes) for (String folder : status.getNewFolders()) { FileObject targetFile = targetFolder.resolveFile(folder); if (!targetFile.exists()) { log.info("Adding new overlay folder " + targetFolder.getName().getRelativeName(targetFile.getName())); targetFile.createFolder(); } } // Perform resource changes boolean conflictFileCreated = false; for (ChangedDocument resource : status.getChangedDocuments().values()) { if (performChange(resource, originalDesignProvider, status, targetEncoding, targetFolder, log, validator)) { conflictFileCreated = true; } } // Overwrite plugin version status.getOverlayData().setBasepluginVersion(baseId.getVersion().toString()); // Write new overlay data file FileObject targetFCFolder = targetFolder.resolveFile(DesignDirectory.FOLDERNAME_FILES); FileObject systemFC = targetFCFolder.resolveFile("system"); FileObject overlayDataFile = systemFC.resolveFile(OverlayDesignProvider.OVERLAY_DATA_FILE); OutputStream out = new BufferedOutputStream(overlayDataFile.getContent().getOutputStream(false)); status.getOverlayData().write(out); out.flush(); out.close(); // Eventually update base-csconfig.xml FileObject baseCsConfigFile = originalDesignProvider.getFilesFolder().resolveFile("system/csconfig.xml"); if (baseCsConfigFile.exists()) { String sourceHash = MD5HashingInputStream.getStreamHash(baseCsConfigFile.getContent().getInputStream()); String targetHash = ""; FileObject baseCsConfigFileOnOverlay = systemFC.resolveFile("base-csconfig.xml"); if (baseCsConfigFileOnOverlay.exists()) { targetHash = MD5HashingInputStream .getStreamHash(baseCsConfigFileOnOverlay.getContent().getInputStream()); } if (!sourceHash.equals(targetHash)) { baseCsConfigFileOnOverlay.delete(); FileUtil.copyContent(baseCsConfigFile, baseCsConfigFileOnOverlay); } } // Eventually update the dependency to the base plugin on csconfig.xml's plugin config FileObject overlayCsConfigFile = systemFC.resolveFile("csconfig.xml"); if (overlayCsConfigFile.exists()) { CSConfig overlayCsConfig = CSConfig.load(overlayCsConfigFile); if (overlayCsConfig.getPluginConfig() != null) { boolean dependencyUpdated = false; for (PluginID id : overlayCsConfig.getPluginConfig().getDependencies()) { if (id.getUniqueName().equals(baseId.getUniqueName()) && !id.getVersion().equals(baseId.getVersion())) { Version dependencyVersion = new Version(baseId.getVersion().getMajorVersion(), baseId.getVersion().getMinorVersion(), baseId.getVersion().getMaintenanceVersion(), baseId.getVersion().getPatchVersion(), 0); id.setVersion(dependencyVersion); dependencyUpdated = true; } } if (dependencyUpdated) { log.info("Updating dependency to base plugin in overlay plugin to new version " + baseId.getVersion()); overlayCsConfig.write(overlayCsConfigFile); } } } // Read/Write design configuration model to ensure correct storage versions of csconfig.xml (#00003634) FileObject designDefinitionFile = DesignDirectory.getDesignDefinitionFile(targetFolder); WGADesignConfigurationModel model = new WGADesignConfigurationModel( new File(designDefinitionFile.getName().getPath())); model.saveChanges(); // Clear the overlay status status.overlayWasUpgraded(); return conflictFileCreated; } public boolean upgradeOverlay(PluginDesignProvider originalDesignProvider, OverlayStatus status) throws Exception { return upgradeOverlay(originalDesignProvider.getSourceDesignProvider(), originalDesignProvider.getPluginID(), status, getBaseFolder(), getFileEncoding(), getLog(), _core.getDesignFileValidator()); } public void createDowngradeFiles(PluginDesignProvider originalDesignProvider, OverlayData data) throws Exception { createDowngradeFiles(originalDesignProvider.getSourceDesignProvider(), data, getBaseFolder(), getFileEncoding(), getLog()); } private static boolean performChange(ChangedDocument changedDocument, FileSystemDesignProvider originalDesignProvider, OverlayStatus status, String targetEncoding, FileObject baseFolder, Logger log, DesignFileValidator validator) throws FileSystemException, NoSuchAlgorithmException, UnsupportedEncodingException, IOException, WGDesignSyncException { boolean conflictFileCreated = false; log.info("Updating overlay resource " + changedDocument.getDocumentKey()); // Find files which represent the document in source and target FileObject sourceDocFile = originalDesignProvider.getBaseFolder() .resolveFile(changedDocument.getSourceFilePath()); FileObject targetDocFile = baseFolder.resolveFile(changedDocument.getTargetFilePath()); // Collect files to copy and delete Map<FileObject, FileObject> filesToCopy = new HashMap<FileObject, FileObject>(); List<FileObject> filesToDelete = new ArrayList<FileObject>(); // Collect for file containers: Must traverse container content if (changedDocument.getDocumentKey().getDocType() == WGDocument.TYPE_FILECONTAINER) { if (changedDocument.getChangeType() == ChangeType.NEW) { targetDocFile.createFolder(); } // Copy all files in container from the source to the target for (FileObject sourceFile : sourceDocFile.getChildren()) { if (sourceFile.getType().equals(FileType.FILE)) { if (!isValidDesignFile(sourceFile, validator)) { continue; } filesToCopy.put(sourceFile, targetDocFile.resolveFile(sourceFile.getName().getBaseName())); } } // Delete all files in target that were deployed with previous base version but are deleted in current base version for (FileObject targetFile : targetDocFile.getChildren()) { if (targetFile.getType().equals(FileType.FILE)) { if (!isValidDesignFile(targetFile, validator)) { continue; } FileObject sourceFile = sourceDocFile.resolveFile(targetFile.getName().getBaseName()); if (sourceFile.exists()) { continue; } // Only delete those that were deployed with previous base version and have unaltered content String resourcePath = baseFolder.getName().getRelativeName(targetFile.getName()); ResourceData resourceData = status.getOverlayData().getOverlayResources().get(resourcePath); if (resourceData != null) { String hash = MD5HashingInputStream.getStreamHash(targetFile.getContent().getInputStream()); if (resourceData.getMd5Hash().equals(hash)) { filesToDelete.add(targetFile); } } } } } // Collect for anything else else { filesToCopy.put(sourceDocFile, targetDocFile); } // Copy files for (Map.Entry<FileObject, FileObject> files : filesToCopy.entrySet()) { FileObject sourceFile = files.getKey(); FileObject targetFile = files.getValue(); String resourcePath = baseFolder.getName().getRelativeName(targetFile.getName()); if (changedDocument.getChangeType() == ChangeType.CONFLICT) { // Do a test if the current file is conflicting. If so we write to a conflict file instead InputStream in = new BufferedInputStream(sourceFile.getContent().getInputStream()); String currentHash = MD5HashingInputStream.getStreamHash(in); ResourceData deployedHash = status.getOverlayData().getOverlayResources().get(resourcePath); boolean skipConflict = false; // Conflict on file container: A single file might just be missing in the target. We can safely copy that to the target without treating as conflict (#00002440) if (deployedHash == null && changedDocument.getDocumentKey().getDocType() == WGDocument.TYPE_FILECONTAINER && !targetFile.exists()) { skipConflict = true; } if (!skipConflict && (deployedHash == null || !deployedHash.getMd5Hash().equals(currentHash))) { targetFile = createConflictFile(targetFile); conflictFileCreated = true; log.warn("Modified overlay resource " + resourcePath + " is updated in base design. We write the updated base version to conflict file for manual resolution: " + baseFolder.getName().getRelativeName(targetFile.getName())); } } // Write file InputStream in = new BufferedInputStream(sourceFile.getContent().getInputStream()); MD5HashingOutputStream out = new MD5HashingOutputStream( new BufferedOutputStream(targetFile.getContent().getOutputStream(false))); // Update resource data resourceInToOut(in, originalDesignProvider.getFileEncoding(), out, targetEncoding); OverlayData.ResourceData resourceData = new OverlayData.ResourceData(); resourceData.setMd5Hash(out.getHash()); status.getOverlayData().setOverlayResource(resourcePath, resourceData); } // Delete files for (FileObject fileToDelete : filesToDelete) { String resourcePath = baseFolder.getName().getRelativeName(fileToDelete.getName()); fileToDelete.delete(); status.getOverlayData().removeOverlayResource(resourcePath); } return conflictFileCreated; } protected static FileObject createConflictFile(FileObject targetFile) throws FileSystemException { String conflictFileName = targetFile.getName().getBaseName() + "_ovlconflict." + targetFile.getName().getExtension(); targetFile = targetFile.getParent().resolveFile(conflictFileName); if (targetFile.exists()) { targetFile.delete(); } return targetFile; } private static void determineChangedResources(int resourceType, FileObject categoryFolder, FileObject source, FileObject target, FileObject baseFolder, String sourceEncoding, String targetEncoding, OverlayStatus status, Logger log, DesignFileValidator validator) throws WGDesignSyncException, NoSuchAlgorithmException, IOException { for (FileObject sourceFile : source.getChildren()) { if (!isValidDesignFile(sourceFile, validator)) { continue; } FileObject targetFile = target.resolveFile(sourceFile.getName().getBaseName()); String resourcePath = baseFolder.getName().getRelativeName(targetFile.getName()); if (sourceFile.getType().equals(FileType.FOLDER)) { if (!targetFile.exists()) { status.getNewFolders().add(targetFile.getName().getPath()); } else if (targetFile.getType().equals(FileType.FILE)) { throw new WGDesignSyncException("Unable to apply overlay. Folder '" + baseFolder.getName().getRelativeName(targetFile.getName()) + " already exists as file. Delete it to enable overlay management again"); } determineChangedResources(resourceType, categoryFolder, sourceFile, targetFile, baseFolder, sourceEncoding, targetEncoding, status, log, validator); } else if (sourceFile.getType().equals(FileType.FILE)) { // File does not exist. if (!targetFile.exists()) { // Was it once deployed? ResourceData originalHash = status.getOverlayData().getOverlayResources().get(resourcePath); if (originalHash == null) { // No, so it must be new in base status.addChangedResource(resourceType, sourceFile, targetFile, categoryFolder, OverlayStatus.ChangeType.NEW, resourcePath, null); } else { // Yes, Check if the base file changed since deployment String newHash = MD5HashingInputStream .getStreamHash(sourceFile.getContent().getInputStream()); if (newHash.equals(originalHash.getMd5Hash())) { // Nope. So this is no change. The overlay just chose not to use the file continue; } else { // Yes, so it is indeed a conflict status.addChangedResource(resourceType, sourceFile, targetFile, categoryFolder, OverlayStatus.ChangeType.CONFLICT, resourcePath, null); } } } // File does exist: Determine if is updated in base since the overlay file was deployed else { ResourceData originalHash = status.getOverlayData().getOverlayResources().get(resourcePath); if (originalHash == null) { if (!status.isUpdatedBaseDesign()) { log.info("There is no information about the original deployment state of resource " + resourcePath + ". Setting original deployment state now to the current base version."); OverlayData.ResourceData resource = new OverlayData.ResourceData(); resource.setMd5Hash( MD5HashingInputStream.getStreamHash(sourceFile.getContent().getInputStream())); status.getOverlayData().setOverlayResource(resourcePath, resource); } else { log.info("Cannot update overlay resource " + resourcePath + " as there is no information of its original deployment state."); } continue; } // First determine if the resource really changed from what was distributed String newHash = MD5HashingInputStream.getStreamHash(sourceFile.getContent().getInputStream()); if (newHash.equals(originalHash.getMd5Hash())) { continue; } // Determine if the target file is the same as was distributed. If not then it was user modified, so it is a conflict String currentHash = MD5HashingInputStream .getStreamHash(targetFile.getContent().getInputStream()); if (!currentHash.equals(originalHash.getMd5Hash())) { status.addChangedResource(resourceType, sourceFile, targetFile, categoryFolder, OverlayStatus.ChangeType.CONFLICT, resourcePath, null); } // It is a normal change else { status.addChangedResource(resourceType, sourceFile, targetFile, categoryFolder, OverlayStatus.ChangeType.CHANGED, resourcePath, null); } } } } } private static void determineChangedFileContainerResources(FileObject categoryFolder, FileObject source, FileObject target, FileObject baseFolder, String sourceEncoding, String targetEncoding, OverlayStatus status, Logger log, DesignFileValidator validator) throws WGDesignSyncException, NoSuchAlgorithmException, IOException { String targetResourcePath = baseFolder.getName().getRelativeName(target.getName()); Set<String> involvedFiles = new HashSet<String>(); // Completely new folder is a new file container if (!target.exists()) { status.addChangedResource(WGDocument.TYPE_FILECONTAINER, source, target, categoryFolder, ChangeType.NEW, targetResourcePath, null); // Als add all subfolders for (FileObject sourceFile : source.getChildren()) { if (sourceFile.getType().equals(FileType.FOLDER)) { FileObject targetFile = target.resolveFile(sourceFile.getName().getBaseName()); determineChangedFileContainerResources(categoryFolder, sourceFile, targetFile, baseFolder, sourceEncoding, targetEncoding, status, log, validator); } } return; } if (target.getType().equals(FileType.FILE)) { throw new WGDesignSyncException( "Unable to apply overlay. Folder '" + baseFolder.getName().getRelativeName(target.getName()) + " already exists as file. Delete it to enable overlay management again"); } // Determine change type by iterating through source child files and compare with target boolean overlayChanged = false; boolean baseChanged = false; boolean directConflict = false; for (FileObject sourceFile : source.getChildren()) { if (!isValidDesignFile(sourceFile, validator)) { continue; } FileObject targetFile = target.resolveFile(sourceFile.getName().getBaseName()); String fileResourcePath = baseFolder.getName().getRelativeName(targetFile.getName()); if (sourceFile.getType().equals(FileType.FOLDER)) { // Descend onto subcontainer determineChangedFileContainerResources(categoryFolder, sourceFile, targetFile, baseFolder, sourceEncoding, targetEncoding, status, log, validator); } else if (sourceFile.getType().equals(FileType.FILE)) { // File does not exist. Look if it was once deployed. if (!targetFile.exists()) { ResourceData resourceData = status.getOverlayData().getOverlayResources().get(fileResourcePath); if (resourceData != null) { // Did already exist. But did it have the same content? String newHash = MD5HashingInputStream .getStreamHash(sourceFile.getContent().getInputStream()); if (newHash.equals(resourceData.getMd5Hash())) { overlayChanged = true; involvedFiles.add(sourceFile.getName().getBaseName() + " (removed in overlay)"); } else { baseChanged = true; overlayChanged = true; directConflict = true; involvedFiles.add( sourceFile.getName().getBaseName() + " (removed in overlay, changed in base)"); } } // Did not yet exist. It is a new file in the base version. else { baseChanged = true; involvedFiles.add(sourceFile.getName().getBaseName() + " (new in base)"); } } // File does exist: Determine if is updated in base since the overlay file was deployed else { ResourceData originalHash = status.getOverlayData().getOverlayResources().get(fileResourcePath); if (originalHash == null) { log.warn("There is no information about the original deployment state of resource '" + fileResourcePath + "' so its change status cannot be determined."); OverlayData.ResourceData resource = new OverlayData.ResourceData(); resource.setMd5Hash( MD5HashingInputStream.getStreamHash(sourceFile.getContent().getInputStream())); status.getOverlayData().setOverlayResource(fileResourcePath, resource); continue; } // First determine if the resource really changed from what was distributed String newHash = MD5HashingInputStream.getStreamHash(sourceFile.getContent().getInputStream()); if (newHash.equals(originalHash.getMd5Hash())) { continue; } // Determine if the target file is the same as was distributed. If not then it was user modified, so it is a conflict String currentHash = MD5HashingInputStream .getStreamHash(targetFile.getContent().getInputStream()); if (!currentHash.equals(originalHash.getMd5Hash())) { overlayChanged = true; baseChanged = true; directConflict = true; involvedFiles.add(sourceFile.getName().getBaseName() + " (changed in base and overlay)"); break; } // It is a normal change else { baseChanged = true; involvedFiles.add(sourceFile.getName().getBaseName() + " (changed in base)"); } } } } // Test the target files. Files may have been added there, or files from the previous base version may still be present that got deleted in the new base version. if (!baseChanged || !overlayChanged) { for (FileObject targetFile : target.getChildren()) { FileObject sourceFile = source.resolveFile(targetFile.getName().getBaseName()); if (!sourceFile.exists()) { // Look if it was deployed with the previous base version. String fileResourcePath = baseFolder.getName().getRelativeName(targetFile.getName()); ResourceData resourceData = status.getOverlayData().getOverlayResources().get(fileResourcePath); if (resourceData != null) { // Was deployed. But with same contents? String targetHash = MD5HashingInputStream .getStreamHash(targetFile.getContent().getInputStream()); if (targetHash.equals(resourceData.getMd5Hash())) { // This is a file that was from previous base version and got removed in the new base version. involvedFiles.add(targetFile.getName().getBaseName() + " (removed from base)"); baseChanged = true; } // File got removed in new base version, updated in overlay. Conflict. else { baseChanged = true; overlayChanged = true; directConflict = true; involvedFiles.add(targetFile.getName().getBaseName() + " (removed from base, changed in overlay)"); break; } } else { involvedFiles.add(targetFile.getName().getBaseName() + " (new in overlay)"); overlayChanged = true; } } } } // Determine change type based on the found changes ChangeType changeType = null; if (baseChanged) { if (overlayChanged) { changeType = ChangeType.CONFLICT; } else { changeType = ChangeType.CHANGED; } } if (changeType != null) { status.addChangedResource(WGDocument.TYPE_FILECONTAINER, source, target, categoryFolder, changeType, targetResourcePath, involvedFiles); } } private static void resourceInToOut(InputStream in, String sourceEncoding, OutputStream out, String targetEncoding) throws UnsupportedEncodingException, IOException { if (sourceEncoding != null && targetEncoding != null) { Reader inReader = new InputStreamReader(in, sourceEncoding); Writer outWriter = new OutputStreamWriter(out, targetEncoding); WGUtils.inToOut(inReader, outWriter, 4096); inReader.close(); outWriter.flush(); outWriter.close(); } else { WGUtils.inToOut(in, out, 4096); in.close(); out.flush(); out.close(); } } public boolean isReady() { return (getFsResources() != null); } @Override public boolean isSynchronizeAccess() { return false; } public boolean isNoBackgroundChanges() { return _noBackgroundChanges; } }