package com.razorfish.platforms.intellivault.services.impl; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.ui.Messages; import com.razorfish.platforms.intellivault.actions.VaultOperationDirectory; import com.razorfish.platforms.intellivault.config.IntelliVaultCRXRepository; import com.razorfish.platforms.intellivault.config.IntelliVaultOperationConfig; import com.razorfish.platforms.intellivault.exceptions.IntelliVaultException; import com.razorfish.platforms.intellivault.filter.VaultImportFilter; import com.razorfish.platforms.intellivault.services.IntelliVaultService; import com.razorfish.platforms.intellivault.services.VaultInvokerService; import com.razorfish.platforms.intellivault.utils.FileUtils; import com.razorfish.platforms.intellivault.utils.IntelliVaultConstants; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; /** * The IntelliVault service handles all of the logic around calling vault such as copying files, settting up vault * configurations, etc. */ public class IntelliVaultServiceImpl implements IntelliVaultService { public static final String CHECKOUT = "co"; private static final String IMPORT = "import"; private static final String EXPORT = "export"; public static final String DEBUG = "debug"; public static final String FILTER = "--filter"; public static final String CREDENTIALS = "--credentials"; public static final String VERBOSE = "--verbose"; public static final String FORCE = "--force"; public static final String LOG_LEVEL = "--log-level"; public static final String WORKSPACE_ROOT_PATH = "/crx/server"; public static final String CREDENTIALS_SEPERATOR = ":"; public static final String NEW_LINE_CHAR = "\n"; private static final List<String> TOP_LEVEL_JCR_PATHS = new ArrayList<String>() { { add("/"); add("/apps"); add("/libs"); add("/etc"); add("/home"); add("/var"); add("/bin"); add("/tmp"); add("/content"); } }; // private PrintStream sysOut; private OutputStream logOut; // private File logFile; private boolean isError; private String errorMsg; private static final Logger log = Logger.getInstance(IntelliVaultServiceImpl.class); @Override public void vaultExport(final IntelliVaultCRXRepository repository, final IntelliVaultOperationConfig opConf, final VaultOperationDirectory exportOpDir, final ProgressIndicator progressIndicator, ConsoleView console) throws IntelliVaultException { progressIndicator.setText2("Preparing export"); if (TOP_LEVEL_JCR_PATHS.contains(exportOpDir.getJcrPath())) { throw new IntelliVaultException("Cannot export top level directory " + exportOpDir.getJcrPath() + ". Please select a valid sub-path."); } isError = false; errorMsg = null; final File exportBaseDir = FileUtils.createTempDirectory(opConf.getTempDirectory()); try { final List<String> jcrPaths = new ArrayList<String>(); jcrPaths.add(exportOpDir.getJcrPath()); File filterFile = createFilterFile(exportBaseDir, jcrPaths); progressIndicator.setText2("Running VLT Export"); final String[] args = prepareExportArgsList(repository, opConf, exportBaseDir, filterFile); progressIndicator.startNonCancelableSection(); invokeVault(opConf, args, console); progressIndicator.finishNonCancelableSection(); progressIndicator.setText2("Copying export contents to IDEA"); ApplicationManager.getApplication().invokeAndWait(new Runnable() { @Override public void run() { try { FileUtils.copyExportContents(exportOpDir.getPsiDir(), exportBaseDir, exportOpDir.getJcrPath()); } catch (IntelliVaultException e) { // TODO work on this log.error("Error copying contents.", e); Messages.showErrorDialog(e.getLocalizedMessage(), "IntelliVault Error!"); } } }, ModalityState.any()); } finally { if (!opConf.isDebug()) { try { FileUtils.deleteDirectoryRecursive(exportBaseDir); } catch (IOException e) { throw new IntelliVaultException("Error while deleting temp contents.", e); } } } } @Override public void vaultImport(final IntelliVaultCRXRepository repository, final IntelliVaultOperationConfig opConf, final VaultOperationDirectory importOpDir, ProgressIndicator progressIndicator, ConsoleView console) throws IntelliVaultException { progressIndicator.setText2("Preparing import"); if (TOP_LEVEL_JCR_PATHS.contains(importOpDir.getJcrPath())) { throw new IntelliVaultException("Cannot import top level directory " + importOpDir.getJcrPath() + ". Please select a valid sub-path."); } isError = false; errorMsg = null; final File importBaseDir = FileUtils.createTempDirectory(opConf.getTempDirectory()); try { final List<String> jcrPaths = new ArrayList<String>(); jcrPaths.add(importOpDir.getJcrPath()); File filterFile = createFilterFile(importBaseDir, jcrPaths); try { FileUtils.writeFile("com/razorfish/platforms/intellivault/settings.xml", filterFile.getParentFile() .getAbsoluteFile() + File.separator + "settings.xml"); FileUtils.writeFile("com/razorfish/platforms/intellivault/config.xml", filterFile.getParentFile() .getAbsoluteFile() + File.separator + "config.xml"); } catch (IOException e) { throw new IntelliVaultException("Error creating import config files.", e); } progressIndicator.setText2("Copying import contents to Temp Directory"); FileUtils.copyImportContents(importBaseDir, importOpDir.getPsiDir(), importOpDir.getJcrPath(), new VaultImportFilter(opConf.getFileIgnorePatterns())); progressIndicator.setText2("Running VLT Import"); String[] args = prepareImportArgsList(repository, opConf, importBaseDir); progressIndicator.startNonCancelableSection(); invokeVault(opConf, args, console); progressIndicator.finishNonCancelableSection(); } finally { if (!opConf.isDebug()) { try { FileUtils.deleteDirectoryRecursive(importBaseDir); } catch (IOException e) { throw new IntelliVaultException("Error while deleting temp contents.", e); } } } } private void invokeVault(final IntelliVaultOperationConfig opConf, final String[] args, ConsoleView console) throws IntelliVaultException { PrintStream sysOut = null; try { sysOut = redirectSysOut(console); } catch (IOException e) { log.error("Error redirecting sysout, ignore.", e); // do nothing, just let it go to sys out } VaultInvokerService vlt = ServiceManager.getService(VaultInvokerService.class); vlt.invokeVault(opConf.getVaultPath(), args); try { restoreSysOut(sysOut); } catch (IOException e) { log.error("Error restoring sysout, ignore.", e); // do nothing, just let it go to sys out } if (isError) { throw new IntelliVaultException(errorMsg); } } private String[] prepareImportArgsList(final IntelliVaultCRXRepository repository, final IntelliVaultOperationConfig opConf, final File importBaseDir) { List<String> argsList = new ArrayList<String>(); if (opConf.isDebug()) { argsList.add(LOG_LEVEL); argsList.add(DEBUG); } argsList.add(IMPORT); argsList.add(repository.getRepoUrl()); argsList.add(importBaseDir.getAbsolutePath()); argsList.add(IntelliVaultConstants.JCR_PATH_SEPERATOR); argsList.add(CREDENTIALS); argsList.add(repository.getUsername() + CREDENTIALS_SEPERATOR + repository.getPassword()); if (opConf.isVerbose()) { argsList.add(VERBOSE); } return argsList.toArray(new String[argsList.size()]); } private String[] prepareExportArgsList(final IntelliVaultCRXRepository repository, final IntelliVaultOperationConfig opConf, final File exportBaseDir, final File filterFile) { List<String> argsList = new ArrayList<String>(); if (opConf.isDebug()) { argsList.add(LOG_LEVEL); argsList.add(DEBUG); } argsList.add(CHECKOUT); argsList.add(FILTER); argsList.add(filterFile.getAbsolutePath()); argsList.add(repository.getRepoUrl() + WORKSPACE_ROOT_PATH); argsList.add(exportBaseDir.getAbsolutePath()); argsList.add(CREDENTIALS); argsList.add(repository.getUsername() + CREDENTIALS_SEPERATOR + repository.getPassword()); if (opConf.isVerbose()) { argsList.add(VERBOSE); } return argsList.toArray(new String[argsList.size()]); } /** * Create filter.xml file and populate it's content from the list of jcr paths handled by this operation. * * @param baseDir the base directory for the vault operation (export or import) * @param paths the List of jcr paths to be handled by the operation * @return File representing the filter.xml that was created * @throws IntelliVaultException if an error occurs preventing creation of the file */ private File createFilterFile(final File baseDir, final List<String> paths) throws IntelliVaultException { File filterFile = null; FileOutputStream os = null; try { filterFile = createFilterFile(baseDir); os = new FileOutputStream(filterFile); os.write(("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<workspaceFilter version=\"1.0\">\n") .getBytes("UTF-8")); for (String path : paths) { os.write(("\t<filter root=\"" + path + "\"/>\n").getBytes("UTF-8")); } os.write(("</workspaceFilter>").getBytes()); } catch (IOException e) { throw new IntelliVaultException(e); } finally { if (os != null) { try { os.flush(); os.close(); } catch (IOException e) { throw new IntelliVaultException(e); } } } return filterFile; } /** * Create an empty filter.xml file. * * @param baseDir the base directory for the vault operation (export or import) * @return File representing the filter.xml that was created * @throws IOException if an IOError occurs preventing creation of the file */ private File createFilterFile(File baseDir) throws IOException { File filterConfigDir = new File(baseDir.getAbsolutePath() + File.separator + "META-INF" + File.separator + "vault"); filterConfigDir.mkdirs(); File filterFile = new File(filterConfigDir.getAbsolutePath() + File.separator + "filter.xml"); if (!filterFile.exists()) { filterFile.createNewFile(); } return filterFile; } /** * Redirect the system output for vault to another, custom output stream. Also starts a thread to read that stream * to identify underlying vault errors. * * @param console the console to log messages to * @return PrintStream that formerly was System.out, so that it can be restored later * @throws IOException if an error occurs */ private PrintStream redirectSysOut(final ConsoleView console) throws IOException { PrintStream sysOut = System.out; final PipedInputStream in = new PipedInputStream(); // TODO close logOut logOut = new PipedOutputStream(in); System.setOut(new PrintStream(logOut)); final Thread writerThread = new Thread() { public void run() { log.debug("Starting input listener"); try { BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); for (String line = reader.readLine(); line != null; line = reader.readLine()) { if (line.contains("[ERROR]") || (isError && (line.contains("caused by") || line.contains("at")))) { isError = true; errorMsg += NEW_LINE_CHAR + line; console.print(line + NEW_LINE_CHAR, ConsoleViewContentType.ERROR_OUTPUT); log.error(line); } else { console.print(line + NEW_LINE_CHAR, ConsoleViewContentType.NORMAL_OUTPUT); log.info(line); } } } catch (IOException e) { log.error("Exception reading output stream", e); } log.debug("Input reader closing"); } }; writerThread.start(); return sysOut; } /** * Restore the system output stream * * @param sysOut the output stream to restore to System.out * @throws IOException if an error occurs while restoring */ private void restoreSysOut(PrintStream sysOut) throws IOException { System.setOut(sysOut); logOut.flush(); logOut.close(); } }