/* =============================================================================== * * Part of the InfoGlue Content Management Platform (www.infoglue.org) * * =============================================================================== * * Copyright (C) * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2, as published by the * Free Software Foundation. See the file LICENSE.html for more information. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY, including 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. / 59 Temple * Place, Suite 330 / Boston, MA 02111-1307 / USA. * * =============================================================================== */ package org.infoglue.cms.filesync; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Date; 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 java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.Element; import org.exolab.castor.jdo.Database; import org.infoglue.cms.controllers.kernel.impl.simple.CastorDatabaseService; import org.infoglue.cms.controllers.kernel.impl.simple.ContentController; import org.infoglue.cms.controllers.kernel.impl.simple.ContentStateController; import org.infoglue.cms.controllers.kernel.impl.simple.ContentTypeDefinitionController; import org.infoglue.cms.controllers.kernel.impl.simple.ContentVersionController; import org.infoglue.cms.controllers.kernel.impl.simple.ContentVersionControllerProxy; import org.infoglue.cms.controllers.kernel.impl.simple.DigitalAssetController; import org.infoglue.cms.controllers.kernel.impl.simple.LanguageController; import org.infoglue.cms.controllers.kernel.impl.simple.RepositoryController; import org.infoglue.cms.controllers.kernel.impl.simple.UserControllerProxy; import org.infoglue.cms.entities.content.ContentVO; import org.infoglue.cms.entities.content.ContentVersionVO; import org.infoglue.cms.entities.content.DigitalAssetVO; import org.infoglue.cms.entities.content.SmallestContentVersionVO; import org.infoglue.cms.entities.management.ContentTypeAttribute; import org.infoglue.cms.entities.management.ContentTypeDefinitionVO; import org.infoglue.cms.entities.management.LanguageVO; import org.infoglue.cms.entities.management.RepositoryVO; import org.infoglue.cms.entities.workflow.EventVO; import org.infoglue.cms.exception.Bug; import org.infoglue.cms.exception.ConstraintException; import org.infoglue.cms.exception.SystemException; import org.infoglue.cms.io.FileHelper; import org.infoglue.cms.security.InfoGluePrincipal; import org.infoglue.cms.util.ChangeNotificationController; import org.infoglue.cms.util.CmsPropertyHandler; import org.infoglue.cms.util.dom.DOMBuilder; import org.infoglue.deliver.util.Timer; public class DevelopmentResourcesSyncService implements Runnable { private final static Logger logger = Logger.getLogger(DevelopmentResourcesSyncService.class.getName()); public static String basePath = CmsPropertyHandler.getDiskBasedDeploymentBasePath(); private static DevelopmentResourcesSyncService singleton = null; private WatchService watcher; private Map<WatchKey, Path> watchPathKeyMap; private List<Path> ignorePaths; private AtomicBoolean exit = new AtomicBoolean(false); private static ContentVersionControllerProxy contentVersionControllerProxy = ContentVersionControllerProxy.getController(); public static DevelopmentResourcesSyncService getInstance() throws Exception { return getInstance(false); } public static DevelopmentResourcesSyncService getInstance(boolean skipCaches) throws Exception { if(!CmsPropertyHandler.getEnableDiskBasedDeployment(skipCaches) || CmsPropertyHandler.getDiskBasedDeploymentBasePath().trim().equals("")) { if(singleton != null) { logger.info("Setting singleton to null and Closing watchers..."); singleton.setExit(); singleton = null; } logger.info("Not starting disk sync area - just returning a new instance"); return null; } String basePathCurrently = CmsPropertyHandler.getDiskBasedDeploymentBasePath(); if(!basePathCurrently.equals(basePath)) { logger.info("basePath changed - restart thread.."); if(singleton != null) { singleton.setExit(); } } if(singleton == null) { singleton = new DevelopmentResourcesSyncService(); Thread thread = new Thread (singleton); thread.start(); } return singleton; } public DevelopmentResourcesSyncService() throws Exception { watcher = FileSystems.getDefault().newWatchService(); watchPathKeyMap = new HashMap<WatchKey, Path>(); } public synchronized void run() { logger.info("Running DevelopmentResourcesSyncService..."); try { Path dirContent = Paths.get(basePath + "/content"); walkTreeAndSetWatches(dirContent); Path dirConfig = Paths.get(basePath + "/configuration"); walkTreeAndSetWatches(dirConfig); for (;;) { try { if(!CmsPropertyHandler.getEnableDiskBasedDeployment() || CmsPropertyHandler.getDiskBasedDeploymentBasePath().trim().equals("")) { logger.info("Exiting thread"); watcher.close(); break; } // wait for key to be signaled WatchKey key; try { key = watcher.take(); } catch (ClosedWatchServiceException cw) { return; } catch (InterruptedException x) { return; } Path dir = watchPathKeyMap.get(key); for (WatchEvent<?> event: key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); if (kind == StandardWatchEventKinds.OVERFLOW) { continue; } WatchEvent<Path> ev = (WatchEvent<Path>)event; Path filename = ev.context(); logger.info("child: " + filename); Path child = dir.resolve(filename); logger.info("child: " + child); processEvent(child, kind); } boolean valid = key.reset(); if (!valid) { break; } } catch(Exception e2) { logger.error("Error processing event: " + e2.getMessage(), e2); } } } catch(Exception e) { logger.error("Could not monitor file system: " + e.getMessage(), e); return; } } private synchronized void walkTreeAndSetWatches(Path root) { try { Files.walkFileTree(root, new FileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { if (ignorePaths != null && ignorePaths.contains(dir)) { return FileVisitResult.SKIP_SUBTREE; } else { logger.info("Registering watcher on: " + dir); logger.info("watcher: " + watcher); WatchKey keyContent = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); watchPathKeyMap.put(keyContent, dir); return FileVisitResult.CONTINUE; } } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } }); } catch (IOException e) { // Don't care } } private void processEvent(Path path, WatchEvent.Kind kind) throws Bug, ConstraintException, Exception { String relativePath = getRelativePath(path); logger.info("relativePath: " + relativePath + " - kind: " + kind + "/" + kind.name()); if(path.toFile().isDirectory()) { watchAndHandleNewDirectory(path); } else { if(kind == StandardWatchEventKinds.ENTRY_MODIFY || kind == StandardWatchEventKinds.ENTRY_CREATE) { if(relativePath.startsWith("content")) { handleContentFileChange(path, relativePath); } else if(relativePath.startsWith("configuration")) { handleConfigurationFileChange(path, relativePath); } } } } private String getRelativePath(Path path) { String relativePath = path.toAbsolutePath().toString().replaceFirst(basePath, ""); if(relativePath.startsWith(File.separator)) relativePath = relativePath.substring(1); return relativePath; } private void handleContentFileChange(Path path, String relativePath) throws SystemException, Bug, ConstraintException, Exception { logger.info("path:" + path); String repoName = relativePath.replaceFirst("content", ""); String[] splitted = repoName.split(Pattern.quote(""+File.separator)); logger.info("splitted 0:" + splitted[0]); logger.info("splitted 1:" + splitted[1]); logger.info("splitted 2:" + splitted[2]); String repoNameFromPath = splitted[1]; logger.info("relativePath:" + relativePath); //System.out.println("repoName:" + repoName); String pathLeft = relativePath.replaceFirst(repoNameFromPath, ""); logger.info("pathLeft:" + pathLeft); pathLeft = pathLeft.replaceFirst(Pattern.quote("content" + File.separator), ""); logger.info("pathLeft:" + pathLeft); RepositoryVO repoVO = RepositoryController.getController().getRepositoryVOWithName(repoNameFromPath); logger.info("repoVO:" + repoVO); pathLeft = pathLeft.substring(0, pathLeft.lastIndexOf(File.separator)); logger.info("pathLeft:" + pathLeft); if(pathLeft.endsWith("Assets")) { pathLeft = pathLeft.replaceFirst(Pattern.quote(File.separator + "Assets"), ""); } logger.info("pathLeft:" + pathLeft); InfoGluePrincipal principal = UserControllerProxy.getController().getUser("Administrator"); // Convert to Infoglue friendly path if (!File.separator.equals("/")) { pathLeft = pathLeft.replaceAll(Pattern.quote(File.separator), "/"); } ContentVO contentVO = ContentController.getContentController().getContentVOWithPath(repoVO.getId(), pathLeft, true, principal); LanguageVO masterLanguageVO = LanguageController.getController().getMasterLanguage(contentVO.getRepositoryId()); //Now lets look at versions ContentVersionVO contentVersionVO = ContentVersionController.getContentVersionController().getLatestActiveContentVersionVO(contentVO.getId(), masterLanguageVO.getId()); ContentTypeDefinitionVO ctdVO = ContentTypeDefinitionController.getController().getContentTypeDefinitionVOWithName("HTMLTemplate"); if(contentVersionVO == null) { contentVO.setIsBranch(false); ContentController.getContentController().update(contentVO, ctdVO.getId()); } logger.info("contentVO" + contentVO); if(contentVersionVO != null) { try { logger.info("contentVersionVO:" + contentVersionVO); contentVersionVO = ContentStateController.changeState(contentVersionVO.getId(), ContentVersionVO.WORKING_STATE, "Remote update from deliver", false, principal, contentVersionVO.getContentId(), new ArrayList<EventVO>()); } catch (Exception e) { logger.error("Error when changing state to working: " + e.getMessage(), e); } if (logger.isInfoEnabled()) { logger.info("contentVersionVO (after state change):" + contentVersionVO); } } boolean isNewlyCreatedVersion; if(contentVersionVO == null) { ContentVersionVO newContentVersionVO = new ContentVersionVO(); newContentVersionVO.setVersionComment("New version created by filesync deploy"); newContentVersionVO.setModifiedDateTime(new Date()); newContentVersionVO.setVersionModifier("" + principal.getName()); contentVersionVO = contentVersionControllerProxy.acCreate(principal, contentVO.getId(), masterLanguageVO.getId(), newContentVersionVO); isNewlyCreatedVersion = true; logger.info("contentVersionVO (newly created):" + contentVersionVO); } else { contentVersionVO.setVersionComment("Version updated by filesync deploy"); contentVersionVO.setModifiedDateTime(new Date()); contentVersionVO.setVersionModifier("" + principal.getName()); isNewlyCreatedVersion = false; } logger.info("Parent name:" + path.getParent().toFile().getName()); if(path.getParent().toFile().getName().equals("Assets")) { logger.info("This is an asset: " + path.getParent().toFile().getName()); String assetKey = FilenameUtils.removeExtension(path.getFileName().toString()); DigitalAssetVO assetVO = DigitalAssetController.getController().getDigitalAssetVO(contentVO.getId(), masterLanguageVO.getId(), assetKey, false); logger.info("assetVO:" + assetVO); if(assetVO != null) { contentVersionVO = ContentVersionController.getContentVersionController().deleteDigitalAssetRelation(contentVersionVO.getId(), assetVO.getId(), principal); Thread.sleep(10000); contentVersionVO.setVersionComment("Version updated by filesync deploy"); contentVersionVO.setModifiedDateTime(new Date()); contentVersionVO.setVersionModifier("" + principal.getName()); isNewlyCreatedVersion = false; DigitalAssetVO afterAssetVO = DigitalAssetController.getController().getDigitalAssetVO(contentVO.getId(), masterLanguageVO.getId(), assetKey, false); assetVO = null; } if(assetVO == null) { assetVO = new DigitalAssetVO(); String contentType = "image/png"; if(path.getFileName().toString().endsWith("jpg")) contentType = "image/jpg"; if(path.getFileName().toString().endsWith("jar")) contentType = "application/java-archive"; if(path.getFileName().toString().endsWith("pdf")) contentType = "application/pdf"; assetVO.setAssetContentType(contentType); assetVO.setAssetKey(assetKey); assetVO.setAssetFileName(path.toFile().getName()); assetVO.setAssetFilePath(path.toFile().getPath()); assetVO.setAssetFileSize(new Integer(new Long(path.toFile().length()).intValue())); logger.info("Creating asset....:" + contentVersionVO.getId() + ":" + path.toFile().getName() + ":" + assetVO.getAssetKey()); try { logger.info("Asset file:" + path.toFile().getPath() + ": " + path.toFile().length()); InputStream is = new FileInputStream(path.toFile()); List<Integer> newContentVersionIdList = new ArrayList<Integer>(); assetVO = DigitalAssetController.create(assetVO, is, contentVersionVO.getId(), principal, newContentVersionIdList); is.close(); } catch(Throwable e) { logger.error("An error occurred when we tried to close the fileinput stream and delete the file:" + e.getMessage(), e); } } try { if(CmsPropertyHandler.getApplicationName().equalsIgnoreCase("cms")) ChangeNotificationController.getInstance().notifyListeners(); } catch(Exception e) { logger.error("Error notifying listener " + e.getMessage()); logger.warn("Error notifying listener " + e.getMessage(), e); } } else { logger.info("path:" + path.toFile().getPath() + ":" + path.toFile().length() + ":" + path.toFile().getParentFile().exists() + ":" + path.toFile().getParentFile().getParentFile().exists() + ":" + path.toFile().getParentFile().getParentFile().getParentFile().exists()); String escaped = path.toString().replace(" ", "\\ "); logger.info("escaped:" + escaped + ":" + path.toFile().getPath() + ":" + path.toFile().exists()); String fileContent = null; try { fileContent = FileHelper.getFileAsStringOpt(path.toFile(), "utf-8"); } catch(Exception e) { logger.error("Could not read content in UTF-8 (trying iso-8859-1 next): " + e.getMessage()); try { fileContent = FileHelper.getFileAsStringOpt(path.toFile(), "iso-8859-1"); } catch(Exception e2) { logger.error("Could not read content in iso-8859-1: " + e2.getMessage()); } } if(fileContent != null) { Map<String, String> attributes = new HashMap<String,String>(); String attributeNameFromFile = FilenameUtils.removeExtension(path.getFileName().toString()); logger.info("Adding:" + attributeNameFromFile + "=" + fileContent); attributes.put(attributeNameFromFile, fileContent); if(attributes != null && attributes.size() > 0) { DOMBuilder domBuilder = new DOMBuilder(); Element attributesRoot = null; Document document = null; if (!isNewlyCreatedVersion) { String existingXML = contentVersionVO.getVersionValue(); document = domBuilder.getDocument(existingXML); attributesRoot = (Element)document.getRootElement().element("attributes"); } else { document = domBuilder.createDocument(); Element rootElement = domBuilder.addElement(document, "root"); domBuilder.addAttribute(rootElement, "xmlns", "x-schema:Schema.xml"); attributesRoot = domBuilder.addElement(rootElement, "attributes"); } if(logger.isDebugEnabled()) logger.info("attributesRoot:" + attributesRoot); Iterator<String> attributesIterator = attributes.keySet().iterator(); while(attributesIterator.hasNext()) { String attributeName = attributesIterator.next(); String attributeValue = attributes.get(attributeName); //attributeValue = cleanAttributeValue(attributeValue, allowHTMLContent, allowExternalLinks, allowDollarSigns, allowAnchorSigns); Element attribute = attributesRoot.element(attributeName); if (attribute == null) { attribute = domBuilder.addElement(attributesRoot, attributeName); } attribute.clearContent(); domBuilder.addCDATAElement(attribute, attributeValue.trim()); } contentVersionVO.setVersionValue(document.asXML()); } //System.out.println("Updating template with new version:" + contentVO.getName()); ContentVersionControllerProxy.getController().acUpdate(principal, contentVO.getId(), masterLanguageVO.getId(), contentVersionVO); try { if(CmsPropertyHandler.getApplicationName().equalsIgnoreCase("cms")) ChangeNotificationController.getInstance().notifyListeners(); } catch(Exception e) { logger.error("Error notifying listener " + e.getMessage()); logger.warn("Error notifying listener " + e.getMessage(), e); } } else logger.error("Skipped updating due to error"); } } private void handleConfigurationFileChange(Path path, String relativePath) throws SystemException, Bug, ConstraintException, Exception { String repoName = relativePath.replaceFirst("configuration", ""); String[] splitted = repoName.split(""+File.separator); String configAspectFromPath = splitted[1]; logger.info("relativePath:" + relativePath); logger.info("repoName:" + repoName); String pathLeft = relativePath.replaceFirst(configAspectFromPath, ""); logger.info("pathLeft:" + pathLeft); pathLeft = pathLeft.replaceFirst("configuration" + File.separator, ""); logger.info("pathLeft:" + pathLeft); logger.info("configAspectFromPath:" + configAspectFromPath); if(configAspectFromPath.equalsIgnoreCase("ContentTypeDefinitions")) { String fileContent = FileHelper.getFileAsStringOpt(path.toFile(), "utf-8"); logger.info("fileContent content type: " + path.toFile().getName()); ContentTypeDefinitionVO ctdVO = ContentTypeDefinitionController.getController().getContentTypeDefinitionVOWithName(FilenameUtils.removeExtension(path.toFile().getName())); if(ctdVO != null) { ctdVO.setSchemaValue(fileContent); logger.info("Set schema value to: " + fileContent + " - Name: " + FilenameUtils.removeExtension(path.toFile().getName())); ContentTypeDefinitionController.getController().update(ctdVO); } else { ContentTypeDefinitionVO contentTypeDefinitionVO = new ContentTypeDefinitionVO(); contentTypeDefinitionVO.setName(FilenameUtils.removeExtension(path.toFile().getName())); contentTypeDefinitionVO.setSchemaValue(fileContent); logger.info("Create schema value to: " + fileContent + " - Name: " + FilenameUtils.removeExtension(path.toFile().getName())); ContentTypeDefinitionController.getController().create(contentTypeDefinitionVO); } } else if(configAspectFromPath.equalsIgnoreCase("Portlets")) { //TBD } else if(configAspectFromPath.equalsIgnoreCase("InterceptionPoints")) { //TBD } else if(configAspectFromPath.equalsIgnoreCase("Interceptors")) { //TBD } else if(configAspectFromPath.equalsIgnoreCase("Categories")) { //TBD } else if(configAspectFromPath.equalsIgnoreCase("Languages")) { //TBD } else if(configAspectFromPath.equalsIgnoreCase("Repositories")) { //TBD } } private void watchAndHandleNewDirectory(Path path) throws SystemException, Bug, ConstraintException, Exception { logger.info("watchAndHandleNewDirectory path:" + path); if(!watchPathKeyMap.containsValue(path)) { logger.info("What to do with a dir????? - registering it!!"); WatchKey keyContent = path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); watchPathKeyMap.put(keyContent, path); for(File file : path.toFile().listFiles()) { logger.info("file:" + file); if(file.getName().startsWith(".")) { logger.info("Skipping files with . as start:" + file); continue; } if(!file.isDirectory()) { String relativePath = getRelativePath(file.toPath()); logger.info("Handling new file: " + relativePath); handleContentFileChange(file.toPath(), relativePath); } } } else logger.info("Already a listener on path: " + path); for(File file : path.toFile().listFiles()) { //logger.info("file:" + file); if(file.getName().startsWith(".")) { System.out.println("Skipping files with . as start:" + file); continue; } if(file.isDirectory()) { watchAndHandleNewDirectory(file.toPath()); } } } private static List<Integer> componentVersionIdsToSync = new ArrayList<Integer>(); public void writeChangesToDiskDelayed(Integer contentVersionId) throws Exception { boolean loop = false; StackTraceElement[] stack = Thread.currentThread().getStackTrace(); for(StackTraceElement element : stack) { if(element.getClassName().contains("DevelopmentResourcesSyncService")) { if(loop == true) { logger.info("Was looping - skip it"); return; } else loop = true; } } logger.info("contentVersionId:" + contentVersionId); componentVersionIdsToSync.add(contentVersionId); class WriteChangesToDiskTask implements Runnable { public void run() { try { logger.info("sleep:" + componentVersionIdsToSync); Thread.sleep(1000); Timer t = new Timer(); Set<Integer> localComponentVersionIdsToSync = new HashSet<Integer>(); localComponentVersionIdsToSync.addAll(componentVersionIdsToSync); componentVersionIdsToSync.clear(); for(Integer localComponentVersionId : localComponentVersionIdsToSync) { writeChangesToDisk(localComponentVersionId); } long time = t.getElapsedTime(); if(time > 100) logger.warn("writeChangesToDiskDelayed took a bit to long:" + time); } catch (Exception e) { logger.warn("Failed to sync changes to disc: " + e.getMessage(), e); } } } Thread thread = new Thread(new WriteChangesToDiskTask()); thread.start(); } public void writeChangesToDisk(Integer contentVersionId) throws Exception { logger.info("contentVersionId:" + contentVersionId); ContentVersionVO contentVersionVO = ContentVersionController.getContentVersionController().getContentVersionVOWithId(contentVersionId); logger.info("contentVersionVO:" + contentVersionVO); if(contentVersionVO != null) { try { ContentVO contentVO = ContentController.getContentController().getLocklessContentVOWithId(contentVersionVO.getContentId()); logger.info("contentVO:" + contentVO); if(contentVO == null) { logger.info("Could not find content - could be deleted."); return; } ContentTypeDefinitionVO ctdVO = ContentTypeDefinitionController.getController().getContentTypeDefinitionVOWithId(contentVO.getContentTypeDefinitionId()); logger.info("ctdVO:" + ctdVO); List<ContentTypeAttribute> attributes = ContentTypeDefinitionController.getController().getContentTypeAttributes(ctdVO.getSchemaValue()); logger.info("attributes:" + attributes); for(ContentTypeAttribute attribute : attributes) { String attributeValue = ContentVersionController.getContentVersionController().getAttributeValue(contentVersionVO, attribute.getName(), false); logger.info("attributeValue:" + attributeValue); if(attributeValue.length() > 0) { RepositoryVO repoVO = RepositoryController.getController().getRepositoryVOWithId(contentVO.getRepositoryId()); String path = basePath + File.separator + "content" + File.separator + repoVO.getName(); String addition = contentVO.getName() + File.separator + attribute.getName(); if(attribute.getName().equals("Template")) addition = addition + ".jsp"; else if(attribute.getName().equals("ComponentLabels")) addition = addition + ".properties"; else if(attribute.getName().equals("ComponentProperties")) addition = addition + ".xml"; else if(attribute.getName().equals("Name")) addition = addition + ".txt"; else addition = addition + ".txt"; logger.info("path:" + path); ContentVO parentContentVO = ContentController.getContentController().getLocklessContentVOWithId(contentVO.getParentContentId()); while(parentContentVO != null) { if(parentContentVO.getParentContentId() != null) { addition = parentContentVO.getName() + File.separator + addition; logger.info("parentContentVO:" + parentContentVO.getName()); parentContentVO = ContentController.getContentController().getLocklessContentVOWithId(parentContentVO.getParentContentId()); } else logger.info("Was root...:" + path); if(parentContentVO.getParentContentId() == null || parentContentVO.getParentContentId() == -1) break; } logger.info("FullPath: " + path + File.separator + addition); File file = new File(path + File.separator + addition); file.getParentFile().mkdirs(); FileHelper.writeToFile(file, attributeValue, false); } } } catch(Exception e) { logger.error("Error writing changes to disk: " + e.getMessage(), e); } } } private static List<Integer> assetIdsToSync = new ArrayList<Integer>(); public void writeAssetChangesToDiskDelayed(Integer assetId) throws Exception { boolean loop = false; StackTraceElement[] stack = Thread.currentThread().getStackTrace(); for(StackTraceElement element : stack) { if(element.getClassName().contains("DevelopmentResourcesSyncService")) { if(loop == true) { logger.info("Was looping - skip it"); return; } else loop = true; } } logger.info("assetId:" + assetId); assetIdsToSync.add(assetId); class WriteChangesToDiskTask implements Runnable { public void run() { try { logger.info("sleep:" + componentVersionIdsToSync); Thread.sleep(1000); Timer t = new Timer(); Set<Integer> localAssetIdsToSync = new HashSet<Integer>(); localAssetIdsToSync.addAll(assetIdsToSync); assetIdsToSync.clear(); for(Integer localAssetId : localAssetIdsToSync) { writeAssetChangesToDisk(localAssetId); } long time = t.getElapsedTime(); if(time > 100) logger.warn("writeAssetChangesToDiskDelayed took a bit to long:" + time); } catch (Exception e) { logger.warn("Failed to sync changes to disc: " + e.getMessage(), e); } } } Thread thread = new Thread(new WriteChangesToDiskTask()); thread.start(); } public void writeAssetChangesToDisk(Integer digitalAssetId) throws Exception { logger.info("digitalAssetId:" + digitalAssetId); DigitalAssetVO assetVO = DigitalAssetController.getDigitalAssetVOWithId(digitalAssetId); logger.info("assetVO:" + assetVO.getAssetKey()); List<SmallestContentVersionVO> contentVersionVOList = DigitalAssetController.getController().getContentVersionVOListConnectedToAssetWithId(digitalAssetId); for(SmallestContentVersionVO contentVersionVO : contentVersionVOList) { logger.info("contentVersionVO:" + contentVersionVO); try { ContentVO contentVO = ContentController.getContentController().getContentVOWithId(contentVersionVO.getContentId()); logger.info("contentVO:" + contentVO); RepositoryVO repoVO = RepositoryController.getController().getRepositoryVOWithId(contentVO.getRepositoryId()); String fileName = assetVO.getAssetKey() + assetVO.getAssetFileName().substring(assetVO.getAssetFileName().lastIndexOf(".")); String addition = contentVO.getName() + File.separator + "Assets"; String path = basePath + File.separator + "content" + File.separator + repoVO.getName(); logger.info("path:" + path); ContentVO parentContentVO = ContentController.getContentController().getContentVOWithId(contentVO.getParentContentId()); while(parentContentVO != null) { addition = parentContentVO.getName() + File.separator + addition; if(parentContentVO.getParentContentId() == null) { System.out.println("Stopping at addition: " + addition); break; } else { parentContentVO = ContentController.getContentController().getContentVOWithId(parentContentVO.getParentContentId()); if(parentContentVO.getParentContentId() == null || parentContentVO.getParentContentId() == -1) break; } } logger.info("FullPath: " + path + File.separator + addition); File file = new File(path + File.separator + addition); file.mkdirs(); Database db = CastorDatabaseService.getDatabase(); try { db.begin(); DigitalAssetController.dumpDigitalAsset(assetVO, fileName, path + File.separator + addition, db); db.commit(); } catch(Exception e) { db.rollback(); } finally { db.close(); } } catch(Exception e) { e.printStackTrace(); } } } public void setExit() { this.exit.set(true); try { logger.info("Closing watchers..."); watcher.close(); singleton = null; } catch (IOException e) { e.printStackTrace(); } } }