/**
* 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