/** * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 . * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ package org.jboss.loom.actions; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.jboss.loom.ctx.MigrationContext; import org.jboss.loom.ex.MigrationException; import org.jboss.loom.spi.IMigrator; /** * Implements lifecycle methods which manage the state, * and some properties (context, origin message, origin stacktrace, origin migrator, warnings, dependencies). * * @author Ondrej Zizka, ozizka at redhat.com * <p/> * TODO: Introduce do***(), eg. doBackup(), to manage the states here, not in the impl. */ public abstract class AbstractStatefulAction implements IMigrationAction { IMigrationAction.State state = State.INITIAL; private MigrationContext ctx; private String originMessage; private StackTraceElement originStacktrace; private Class<? extends IMigrator> fromMigrator; private List<String> warnings = new LinkedList(); private List<IMigrationAction> deps = new LinkedList(); public AbstractStatefulAction(){ this.originStacktrace = getNonActionCallee( Thread.currentThread().getStackTrace() ); } public AbstractStatefulAction( Class<? extends IMigrator> fromMigrator ) { this(); this.fromMigrator = fromMigrator; } private StackTraceElement getNonActionCallee( StackTraceElement[] stackTrace ) { //return Thread.currentThread().getStackTrace()[4]; // 0 - Thread.getStackTrace(). // 1 - This method. // 2 - This constructor. // 3 - *Action constructor. // 4 - Whatever called new CliCommandAction. // Could be better, e.g. first non-constructor after 2. for( StackTraceElement elm : stackTrace ) { if( IMigrationAction.class.isAssignableFrom( elm.getClass() ) ) return elm; } return stackTrace[4]; // Fallback; will not happen, unless we put main() to this class or so. } public AbstractStatefulAction addWarning(String text) { warnings.add(text); return this; } public void checkState(IMigrationAction.State... states) { for( State state : states ) { if (this.state == state) return; } throw new RuntimeException("Action not in expected states " + StringUtils.join( states, " " ) + ", but in " +this.getState()+ ":\n " + this.toDescription()); } //<editor-fold defaultstate="collapsed" desc="get/set"> @Override public void setMigrationContext(MigrationContext ctx) { this.ctx = ctx; } @Override public MigrationContext getMigrationContext() { return this.ctx; } @Override public IMigrationAction.State getState() { return state; } public void setState(IMigrationAction.State state) { this.state = state; } @Override public StackTraceElement getOriginStackTrace(){ return originStacktrace; } @Override public String getOriginMessage() { return originMessage; } public AbstractStatefulAction setOriginMessage(String msg) { this.originMessage = msg; return this; } @Override public Class<? extends IMigrator> getFromMigrator(){ return fromMigrator; } @Override public List<String> getWarnings() { return warnings; } //</editor-fold> protected boolean isAfterBackup() { return this.state.ordinal() >= State.BACKED_UP.ordinal(); } protected boolean isAfterPerform() { return this.state.ordinal() >= State.DONE.ordinal(); } /* ----- Dependency stuff ----- */ @Override public List<IMigrationAction> getDependencies() { return this.deps; } @Override public IMigrationAction addDependency( IMigrationAction dep ) { this.deps.add( dep ); return this; } /** * {@inheritDoc} */ @Override public int dependsOn( IMigrationAction other ) throws CircularDependencyException { Set<IMigrationAction> visited = new HashSet(); visited.add( this ); visited.add( other ); return dependsOn( other, visited ); } /** * {@inheritDoc} */ private int dependsOn( IMigrationAction other, Set<IMigrationAction> visited ) throws CircularDependencyException { if( this.getDependencies().isEmpty() ) return -1; if( this.equals( other ) ) return 0; if( this.getDependencies().contains( other ) ) return 1; int minDist = Integer.MAX_VALUE; for( IMigrationAction dep : this.getDependencies() ){ if( visited.contains( dep ) ) throw new CircularDependencyException(this, dep); int dist = dep.dependsOn( other ); if( dist > 0 ) minDist = Math.min( dist, minDist ); } if( minDist == Integer.MAX_VALUE ) return -1; else return minDist + 1; } public static class CircularDependencyException extends MigrationException { public CircularDependencyException( IMigrationAction a, IMigrationAction b ) { super("Circular dependency of actions - somewhere between these:\n\n" + a.toDescription() + "\n\n" + b.toDescription()); } } /** * Sorting nodes of one action's dependency tree. */ public static List<IMigrationAction> sortNodes_LeavesFirst( IMigrationAction action ){ List<IMigrationAction> retNodes = new LinkedList(); sortNodes_LeavesFirst( action, retNodes ); return retNodes; } private static void sortNodes_LeavesFirst( IMigrationAction action, List<IMigrationAction> retNodes ) { for( IMigrationAction dep : action.getDependencies() ){ sortNodes_LeavesFirst( dep, retNodes ); } retNodes.add( action ); } }// class