package org.springframework.roo.process.manager.internal; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.osgi.service.component.ComponentContext; import org.springframework.roo.file.monitor.NotifiableFileMonitorService; import org.springframework.roo.file.monitor.event.FileDetails; import org.springframework.roo.file.undo.CreateDirectory; import org.springframework.roo.file.undo.CreateFile; import org.springframework.roo.file.undo.DeleteDirectory; import org.springframework.roo.file.undo.DeleteFile; import org.springframework.roo.file.undo.FilenameResolver; import org.springframework.roo.file.undo.UndoEvent; import org.springframework.roo.file.undo.UndoListener; import org.springframework.roo.file.undo.UndoManager; import org.springframework.roo.file.undo.UpdateFile; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.process.manager.ProcessManager; /** * Default implementation of {@link FileManager}. * * @author Ben Alex * @since 1.0 */ @Component @Service public class DefaultFileManager implements FileManager, UndoListener { /** key: file identifier, value: new description of change */ private final Map<String, String> deferredDescriptionOfChanges = new LinkedHashMap<String, String>(); /** key: file identifier, value: new textual content */ private final Map<String, String> deferredFileWrites = new LinkedHashMap<String, String>(); @Reference private NotifiableFileMonitorService fileMonitorService; @Reference private FilenameResolver filenameResolver; @Reference private ProcessManager processManager; @Reference private UndoManager undoManager; protected void activate(final ComponentContext context) { undoManager.addUndoListener(this); } public void clear() { deferredFileWrites.clear(); deferredDescriptionOfChanges.clear(); } public void commit() { final Map<String, String> toRemove = new LinkedHashMap<String, String>( deferredFileWrites); try { for (final Entry<String, String> entry : toRemove.entrySet()) { final String fileIdentifier = entry.getKey(); final String newContents = entry.getValue(); if (StringUtils.isNotBlank(newContents)) { createOrUpdateTextFileIfRequired(fileIdentifier, newContents, StringUtils .stripToEmpty(deferredDescriptionOfChanges .get(fileIdentifier))); } else if (exists(fileIdentifier)) { delete(fileIdentifier, "empty"); } } } finally { for (final String remove : toRemove.keySet()) { deferredFileWrites.remove(remove); } deferredDescriptionOfChanges.clear(); } } public FileDetails createDirectory(final String fileIdentifier) { Validate.notNull(fileIdentifier, "File identifier required"); final File actual = new File(fileIdentifier); Validate.isTrue(!actual.exists(), "File '" + fileIdentifier + "' already exists"); try { fileMonitorService.notifyCreated(actual.getCanonicalPath()); } catch (final IOException ignored) { } new CreateDirectory(undoManager, filenameResolver, actual); return new FileDetails(actual, actual.lastModified()); } public MutableFile createFile(final String fileIdentifier) { Validate.notNull(fileIdentifier, "File identifier required"); final File actual = new File(fileIdentifier); Validate.isTrue(!actual.exists(), "File '" + fileIdentifier + "' already exists"); try { fileMonitorService.notifyCreated(actual.getCanonicalPath()); final File parentDirectory = new File(actual.getParent()); if (!parentDirectory.exists()) { createDirectory(parentDirectory.getCanonicalPath()); } } catch (final IOException ignored) { } new CreateFile(undoManager, filenameResolver, actual); final ManagedMessageRenderer renderer = new ManagedMessageRenderer( filenameResolver, actual, true); renderer.setIncludeHashCode(processManager.isDevelopmentMode()); return new DefaultMutableFile(actual, null, renderer); } public void createOrUpdateTextFileIfRequired(final String fileIdentifier, final String newContents, final boolean writeImmediately) { createOrUpdateTextFileIfRequired(fileIdentifier, newContents, "", writeImmediately); } private void createOrUpdateTextFileIfRequired(final String fileIdentifier, final String newContents, final String descriptionOfChange) { MutableFile mutableFile = null; if (exists(fileIdentifier)) { // First verify if the file has even changed final File file = new File(fileIdentifier); String existing = null; try { existing = FileUtils.readFileToString(file); } catch (final IOException ignored) { } if (!newContents.equals(existing)) { mutableFile = updateFile(fileIdentifier); } } else { mutableFile = createFile(fileIdentifier); Validate.notNull(mutableFile, "Could not create file '" + fileIdentifier + "'"); } if (mutableFile != null) { OutputStream outputStream = null; try { if (StringUtils.isNotBlank(descriptionOfChange)) { mutableFile.setDescriptionOfChange(descriptionOfChange); } outputStream = mutableFile.getOutputStream(); IOUtils.write(newContents, outputStream); } catch (final IOException e) { throw new IllegalStateException("Could not output '" + mutableFile.getCanonicalPath() + "'", e); } finally { IOUtils.closeQuietly(outputStream); } } } public void createOrUpdateTextFileIfRequired(final String fileIdentifier, final String newContents, final String descriptionOfChange, final boolean writeImmediately) { if (writeImmediately) { createOrUpdateTextFileIfRequired(fileIdentifier, newContents, descriptionOfChange); } else { deferredFileWrites.put(fileIdentifier, newContents); String deferredDescriptionOfChange = StringUtils.defaultIfEmpty( deferredDescriptionOfChanges.get(fileIdentifier), ""); if (StringUtils.isNotBlank(deferredDescriptionOfChange) && !deferredDescriptionOfChange.trim().endsWith(";")) { deferredDescriptionOfChange += "; "; } deferredDescriptionOfChanges.put( fileIdentifier, deferredDescriptionOfChange + StringUtils.stripToEmpty(descriptionOfChange)); } } protected void deactivate(final ComponentContext context) { undoManager.removeUndoListener(this); } public void delete(final String fileIdentifier) { delete(fileIdentifier, null); } public void delete(final String fileIdentifier, final String reasonForDeletion) { if (StringUtils.isBlank(fileIdentifier)) { return; } final File actual = new File(fileIdentifier); Validate.isTrue(actual.exists(), "File '" + fileIdentifier + "' does not exist"); try { fileMonitorService.notifyDeleted(actual.getCanonicalPath()); } catch (final IOException ignored) { } if (actual.isDirectory()) { new DeleteDirectory(undoManager, filenameResolver, actual, reasonForDeletion); } else { new DeleteFile(undoManager, filenameResolver, actual, reasonForDeletion); } } public boolean exists(final String fileIdentifier) { Validate.notBlank(fileIdentifier, "File identifier required"); return new File(fileIdentifier).exists(); } public SortedSet<FileDetails> findMatchingAntPath(final String antPath) { return fileMonitorService.findMatchingAntPath(antPath); } public InputStream getInputStream(final String fileIdentifier) { if (deferredFileWrites.containsKey(fileIdentifier)) { return new BufferedInputStream(new ByteArrayInputStream( deferredFileWrites.get(fileIdentifier).getBytes())); } final File file = new File(fileIdentifier); Validate.isTrue(file.exists(), "File '" + fileIdentifier + "' does not exist"); Validate.isTrue(file.isFile(), "Path '" + fileIdentifier + "' is not a file"); try { return new BufferedInputStream(new FileInputStream(new File( fileIdentifier))); } catch (final IOException ioe) { throw new IllegalStateException( "Could not obtain input stream to file '" + fileIdentifier + "'", ioe); } } public void onUndoEvent(final UndoEvent event) { if (event.isUndoing()) { clear(); } else { // It's a flush or a reset event commit(); } } public FileDetails readFile(final String fileIdentifier) { Validate.notNull(fileIdentifier, "File identifier required"); final File f = new File(fileIdentifier); if (!f.exists()) { return null; } return new FileDetails(f, f.lastModified()); } public int scan() { return fileMonitorService.scanNotified(); } public MutableFile updateFile(final String fileIdentifier) { Validate.notNull(fileIdentifier, "File identifier required"); final File actual = new File(fileIdentifier); Validate.isTrue(actual.exists(), "File '" + fileIdentifier + "' does not exist"); new UpdateFile(undoManager, filenameResolver, actual); final ManagedMessageRenderer renderer = new ManagedMessageRenderer( filenameResolver, actual, false); renderer.setIncludeHashCode(processManager.isDevelopmentMode()); return new DefaultMutableFile(actual, fileMonitorService, renderer); } }