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 java.util.logging.Logger;
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.Service;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
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;
import org.springframework.roo.support.logging.HandlerUtils;
/**
* Default implementation of {@link FileManager}.
*
* @author Ben Alex
* @since 1.0
*/
@Component
@Service
public class DefaultFileManager implements FileManager, UndoListener {
protected final static Logger LOGGER = HandlerUtils.getLogger(DefaultFileManager.class);
/** 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>();
// ------------ OSGi component attributes ----------------
private BundleContext context;
private NotifiableFileMonitorService fileMonitorService;
private FilenameResolver filenameResolver;
private ProcessManager processManager;
private UndoManager undoManager;
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
if (undoManager == null) {
undoManager = getUndoManager();
}
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) {
if (fileMonitorService == null) {
fileMonitorService = getFileMonitorService();
}
if (filenameResolver == null) {
filenameResolver = getFileNameResolver();
}
if (undoManager == null) {
undoManager = getUndoManager();
}
Validate.notNull(fileIdentifier, "File identifier required");
Validate.notNull(fileMonitorService, "FileMonitorService required");
Validate.notNull(filenameResolver, "FilenameResolver required");
Validate.notNull(undoManager, "UndoManager required");
final File actual = new File(fileIdentifier);
Validate.isTrue(!actual.exists(), "File '%s' already exists", fileIdentifier);
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) {
if (fileMonitorService == null) {
fileMonitorService = getFileMonitorService();
}
if (processManager == null) {
processManager = getProcessManager();
}
if (filenameResolver == null) {
filenameResolver = getFileNameResolver();
}
if (undoManager == null) {
undoManager = getUndoManager();
}
Validate.notNull(fileIdentifier, "File identifier required");
Validate.notNull(fileMonitorService, "FileMonitorService required");
Validate.notNull(processManager, "ProcessManager required");
Validate.notNull(filenameResolver, "FilenameResolver required");
Validate.notNull(undoManager, "UndoManager required");
final File actual = new File(fileIdentifier);
Validate.isTrue(!actual.exists(), "File '%s' already exists", fileIdentifier);
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 '%s'", 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) {
if (undoManager == null) {
undoManager = getUndoManager();
}
Validate.notNull(undoManager, "UndoManager is required");
undoManager.removeUndoListener(this);
}
public void delete(final String fileIdentifier) {
delete(fileIdentifier, null);
}
public void delete(final String fileIdentifier, final String reasonForDeletion) {
if (fileMonitorService == null) {
fileMonitorService = getFileMonitorService();
}
if (filenameResolver == null) {
filenameResolver = getFileNameResolver();
}
if (undoManager == null) {
undoManager = getUndoManager();
}
Validate.notNull(fileMonitorService, "FileMonitorService required");
Validate.notNull(filenameResolver, "FilenameResolver required");
Validate.notNull(undoManager, "UndoManager is required");
if (StringUtils.isBlank(fileIdentifier)) {
return;
}
final File actual = new File(fileIdentifier);
Validate.isTrue(actual.exists(), "File '%s' does not exist", fileIdentifier);
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) {
if (fileMonitorService == null) {
fileMonitorService = getFileMonitorService();
}
Validate.notNull(fileMonitorService, "FileMonitorService required");
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 '%s' does not exist", fileIdentifier);
Validate.isTrue(file.isFile(), "Path '%s' is not a file", fileIdentifier);
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() {
if (fileMonitorService == null) {
fileMonitorService = getFileMonitorService();
}
Validate.notNull(fileMonitorService, "FileMonitorService required");
return fileMonitorService.scanNotified();
}
public MutableFile updateFile(final String fileIdentifier) {
if (fileMonitorService == null) {
fileMonitorService = getFileMonitorService();
}
if (processManager == null) {
processManager = getProcessManager();
}
if (filenameResolver == null) {
filenameResolver = getFileNameResolver();
}
if (undoManager == null) {
undoManager = getUndoManager();
}
Validate.notNull(fileIdentifier, "File identifier required");
Validate.notNull(fileMonitorService, "FileMonitorService required");
Validate.notNull(processManager, "ProcessManager required");
Validate.notNull(filenameResolver, "FilenameResolver required");
Validate.notNull(undoManager, "UndoManager required");
final File actual = new File(fileIdentifier);
Validate.isTrue(actual.exists(), "File '%s' does not exist", fileIdentifier);
new UpdateFile(undoManager, filenameResolver, actual);
final ManagedMessageRenderer renderer =
new ManagedMessageRenderer(filenameResolver, actual, false);
renderer.setIncludeHashCode(processManager.isDevelopmentMode());
return new DefaultMutableFile(actual, fileMonitorService, renderer);
}
public NotifiableFileMonitorService getFileMonitorService() {
// Get all Services implement NotifiableFileMonitorService interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(NotifiableFileMonitorService.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (NotifiableFileMonitorService) this.context.getService(ref);
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load NotifiableFileMonitorService on DefaultFileManager.");
return null;
}
}
public ProcessManager getProcessManager() {
// Get all Services implement ProcessManager interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(ProcessManager.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (ProcessManager) this.context.getService(ref);
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load ProcessManager on DefaultFileManager.");
return null;
}
}
public FilenameResolver getFileNameResolver() {
// Get all Services implement FilenameResolver interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(FilenameResolver.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (FilenameResolver) this.context.getService(ref);
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load FilenameResolver on DefaultFileManager.");
return null;
}
}
public UndoManager getUndoManager() {
// Get all Services implement UndoManager interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(UndoManager.class.getName(), null);
for (ServiceReference<?> ref : references) {
return (UndoManager) this.context.getService(ref);
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load UndoManager on DefaultFileManager.");
return null;
}
}
}