package org.springframework.roo.process.manager.internal; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.FrameworkListener; import org.osgi.service.component.ComponentContext; import org.osgi.service.startlevel.StartLevel; import org.springframework.roo.file.monitor.FileMonitorService; import org.springframework.roo.file.monitor.MonitoringRequest; import org.springframework.roo.file.monitor.NotifiableFileMonitorService; import org.springframework.roo.file.undo.UndoManager; import org.springframework.roo.process.manager.ActiveProcessManager; import org.springframework.roo.process.manager.CommandCallback; import org.springframework.roo.process.manager.ProcessManager; import org.springframework.roo.process.manager.event.AbstractProcessManagerStatusPublisher; import org.springframework.roo.process.manager.event.ProcessManagerStatus; import org.springframework.roo.support.logging.HandlerUtils; import org.springframework.roo.support.osgi.OSGiUtils; /** * Default implementation of {@link ProcessManager} interface. * * @author Ben Alex * @since 1.0 */ @Component(immediate = true) @Service public class DefaultProcessManager extends AbstractProcessManagerStatusPublisher implements ProcessManager { private static final Logger LOGGER = HandlerUtils .getLogger(DefaultProcessManager.class); private boolean developmentMode = false; @Reference private FileMonitorService fileMonitorService; private long lastPollDuration = 0; private long lastPollTime = 0; // What time the last poll was completed private long minimumDelayBetweenPoll = -1; // How many ms must pass at @Reference private StartLevel startLevel; @Reference private UndoManager undoManager; private String workingDir; public <T> T execute(final CommandCallback<T> callback) { Validate.notNull(callback, "Callback required"); synchronized (processManagerStatus) { // For us to acquire this lock means no other thread has hold of // process manager status Validate.isTrue( getProcessManagerStatus() == ProcessManagerStatus.AVAILABLE || getProcessManagerStatus() == ProcessManagerStatus.BUSY_EXECUTING, "Unable to execute as another thread has set status to " + getProcessManagerStatus()); setProcessManagerStatus(ProcessManagerStatus.BUSY_EXECUTING); try { return doTransactionally(callback); } catch (final RuntimeException e) { logException(e); throw e; } finally { setProcessManagerStatus(ProcessManagerStatus.AVAILABLE); } } } /** * @return how many milliseconds the last poll execution took to complete (0 * = never ran; >0 = last execution time) */ public long getLastPollDuration() { return lastPollDuration; } /** * @return how many milliseconds must pass between each poll (0 = manual * only; <0 = auto-scaled; >0 = interval) */ public long getMinimumDelayBetweenPoll() { return minimumDelayBetweenPoll; } public boolean isDevelopmentMode() { return developmentMode; } public void setDevelopmentMode(final boolean developmentMode) { this.developmentMode = developmentMode; // To assist with debugging, development mode does not undertake undo // operations undoManager.setUndoEnabled(!developmentMode); } /** * @param minimumDelayBetweenPoll how many milliseconds must pass between * each poll */ public void setMinimumDelayBetweenPoll(final long minimumDelayBetweenPoll) { this.minimumDelayBetweenPoll = minimumDelayBetweenPoll; } public void terminate() { synchronized (processManagerStatus) { // To get this far this thread has a lock on process manager status, // so we control process manager and can terminate its background // timer thread if (getProcessManagerStatus() != ProcessManagerStatus.TERMINATED) { // The thread started above will terminate of its own accord, // given we are shutting down setProcessManagerStatus(ProcessManagerStatus.TERMINATED); } } } public void timerBasedPoll() { try { if (minimumDelayBetweenPoll == 0) { // Manual polling only, we never allow the timer to kick of a // poll return; } long effectiveMinimumDelayBetweenPoll = minimumDelayBetweenPoll; if (effectiveMinimumDelayBetweenPoll < 0) { // A negative minimum delay between poll means auto-scaling is // used if (lastPollDuration < 500) { // We've never done a poll, or they are very fast effectiveMinimumDelayBetweenPoll = 0; } else { // Use the last duration (we might make this sliding scale // in the future) effectiveMinimumDelayBetweenPoll = lastPollDuration; } } final long started = System.currentTimeMillis(); if (started < lastPollTime + effectiveMinimumDelayBetweenPoll) { // Too soon to re-poll return; } backgroundPoll(); // Record the completion time so we can ensure we don't re-poll too // soon lastPollTime = System.currentTimeMillis(); // Compute how many milliseconds it took to run lastPollDuration = lastPollTime - started; if (lastPollDuration == 0) { // Ensure it correctly reflects that it has ever run lastPollDuration = 1; } } catch (final Throwable t) { LOGGER.log(Level.SEVERE, t.getMessage(), t); } } protected void activate(final ComponentContext context) { workingDir = OSGiUtils.getRooWorkingDirectory(context); context.getBundleContext().addFrameworkListener( new FrameworkListener() { public void frameworkEvent(final FrameworkEvent event) { if (startLevel.getStartLevel() >= 99) { // We check we haven't already started, as this // event listener will be called several times at SL // >= 99 if (getProcessManagerStatus() == ProcessManagerStatus.STARTING) { // A proper synchronized process manager status // check will take place in the // completeStartup() method completeStartup(); } } } }); // Now start a thread that will undertake a background poll every second final Thread t = new Thread(new Runnable() { public void run() { // Unsynchronized lookup of terminated status to avoid anything // blocking the termination of the thread while (getProcessManagerStatus() != ProcessManagerStatus.TERMINATED) { // We only bother doing a poll if we seem to be available (a // proper synchronized check happens later) if (getProcessManagerStatus() == ProcessManagerStatus.AVAILABLE) { timerBasedPoll(); } try { Thread.sleep(1000); } catch (final InterruptedException ignoreAndContinue) { } } } }, "Spring Roo Process Manager Background Polling Thread"); t.start(); } protected void deactivate(final ComponentContext context) { // We have lost a required component (eg UndoManager; ROO-1037) terminate(); // Safe to call even if we'd terminated earlier } private boolean backgroundPoll() { // Quickly determine if another thread is running; we don't need to sit // around and wait (we'll get called again in a few hundred milliseconds // anyway) if (getProcessManagerStatus() != ProcessManagerStatus.AVAILABLE) { return false; } synchronized (processManagerStatus) { // Do the check again, now this thread has a lock on // processManagerStatus if (getProcessManagerStatus() != ProcessManagerStatus.AVAILABLE) { throw new IllegalStateException( "Process manager status " + getProcessManagerStatus() + " but background thread acquired synchronization lock"); } setProcessManagerStatus(ProcessManagerStatus.BUSY_POLLING); try { doTransactionally(null); } catch (final Throwable t) { // We don't want a poll failure to cause the background polling // thread to die logException(t); } finally { setProcessManagerStatus(ProcessManagerStatus.AVAILABLE); } } return true; } private void completeStartup() { synchronized (processManagerStatus) { if (getProcessManagerStatus() != ProcessManagerStatus.STARTING) { throw new IllegalStateException("Process manager status " + getProcessManagerStatus() + " but should be STARTING"); } setProcessManagerStatus(ProcessManagerStatus.COMPLETING_STARTUP); try { // Register the initial monitoring request doTransactionally(new MonitoringRequestCommand( fileMonitorService, MonitoringRequest .getInitialSubTreeMonitoringRequest(workingDir), true)); } catch (final Throwable t) { logException(t); } finally { setProcessManagerStatus(ProcessManagerStatus.AVAILABLE); } } } private <T> T doTransactionally(final CommandCallback<T> callback) { T result = null; try { ActiveProcessManager.setActiveProcessManager(this); // Run the requested operation if (callback == null) { fileMonitorService.scanAll(); } else { result = callback.callback(); } // Flush the undo manager so that any changes it has been holding // are written to disk and the file monitor service undoManager.flush(); // Guarantee scans repeat until there are no more changes detected while (fileMonitorService.isDirty()) { if (fileMonitorService instanceof NotifiableFileMonitorService) { ((NotifiableFileMonitorService) fileMonitorService) .scanNotified(); } else { fileMonitorService.scanAll(); } // In case something else happened as a result of event // notifications above undoManager.flush(); } // It all seems to have worked, so clear the undo history setProcessManagerStatus(ProcessManagerStatus.RESETTING_UNDOS); undoManager.reset(); } catch (final RuntimeException e) { // Something went wrong, so attempt to undo try { setProcessManagerStatus(ProcessManagerStatus.UNDOING); throw e; } finally { undoManager.undo(); } } finally { // TODO: Review in consultation with Christian as STS is clearing // active process manager itself // ActiveProcessManager.clearActiveProcessManager(); } return result; } private void logException(final Throwable t) { final Throwable root = ObjectUtils.defaultIfNull( ExceptionUtils.getRootCause(t), t); if (developmentMode) { LOGGER.log(Level.FINE, root.getMessage(), root); } else { String message = root.getMessage(); if (StringUtils.isBlank(message)) { final StackTraceElement[] trace = root.getStackTrace(); if (trace != null && trace.length > 0) { message = root.getClass().getSimpleName() + " at " + trace[0].toString(); } else { message = root.getClass().getSimpleName(); } } LOGGER.log(Level.FINE, message); } } }