/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.patching.runner;
import static org.jboss.as.patching.IoUtils.NO_CONTENT;
import java.io.IOException;
import java.util.Arrays;
import org.jboss.as.patching.PatchingException;
import org.jboss.as.patching.logging.PatchLogger;
import org.jboss.as.patching.metadata.ContentItem;
import org.jboss.as.patching.metadata.ContentModification;
import org.jboss.as.patching.metadata.ContentType;
import org.jboss.as.patching.metadata.ModificationCondition;
/**
* Basic patching task implementation.
*
* @author Emanuel Muckenhuber
*/
abstract class AbstractPatchingTask<T extends ContentItem> implements PatchingTask {
protected final T contentItem;
protected final PatchingTaskDescription description;
private boolean ignoreApply; // completely ignore the apply step
private boolean skipExecution; // Skip the execution step
protected byte[] backupHash = NO_CONTENT; // The backup hash
AbstractPatchingTask(PatchingTaskDescription description, Class<T> expected) {
this.description = description;
this.contentItem = description.getContentItem(expected);
}
@Override
public T getContentItem() {
return contentItem;
}
@Override
public boolean isRelevant(PatchingTaskContext context) throws PatchingException {
final ModificationCondition condition = description.getModification().getCondition();
if(condition == null) {
return true;
}
return condition.isSatisfied(context);
}
/**
* Backup the content.
*
* @param context the patching context
* @return the hash of the content
* @throws IOException
*/
abstract byte[] backup(PatchingTaskContext context) throws IOException;
/**
* Apply the modification.
*
* @param context the patching context
* @param loader the patch content loader
* @return the actual copied content hash
* @throws IOException
*/
abstract byte[] apply(final PatchingTaskContext context, final PatchContentLoader loader) throws IOException;
/**
* Create the rollback entry.
*
* @param original the original content modification
* @param targetHash the new target hash code (current content)
* @param itemHash the new content item hash (backup content)
* @return the rollback modification information
*/
abstract ContentModification createRollbackEntry(ContentModification original, byte[] targetHash, byte[] itemHash);
/**
* Fail if the copied content is different from the one specified in the metadata. This should be true in most of the
* cases. Only removing a module does not really match this, since we are creating a removed-module rather than
* removing the contents.
*
* @param context the task context
* @return
*/
protected boolean failOnContentMismatch(PatchingTaskContext context) {
return context.getCurrentMode() != PatchingTaskContext.Mode.UNDO;
}
/**
* Get the original modification. Tasks like module remove can override this and fix the hashes based on the created content.
*
* @param targetHash the new target hash code (current content)
* @param itemHash the new content item hash (backup content)
* @return the original modification
*/
protected ContentModification getOriginalModification(byte[] targetHash, byte[] itemHash) {
return description.getModification();
}
/**
* Completely skip the apply step.
*/
protected void setIgnoreApply() {
ignoreApply = true;
}
@Override
public boolean prepare(final PatchingTaskContext context) throws IOException {
// Backup
backupHash = backup(context);
// If the content is already present just resolve any conflict automatically
final byte[] contentHash = contentItem.getContentHash();
if(Arrays.equals(backupHash, contentHash)) {
// Skip execute for misc items only
skipExecution = contentItem.getContentType() == ContentType.MISC && backupHash != NO_CONTENT;
return true;
}
// See if the content matches our expected target
final byte[] expected = description.getModification().getTargetHash();
if(Arrays.equals(backupHash, expected)) {
// Don't resolve conflicts from the history
return ! description.hasConflicts();
}
return false;
}
@Override
public void execute(final PatchingTaskContext context) throws IOException {
if (ignoreApply) {
return;
}
final PatchContentLoader contentLoader = description.getLoader();
final boolean skip = skipExecution | context.isExcluded(contentItem);
final byte[] contentHash;
if(skip) {
contentHash = backupHash; // Reuse the backup hash
} else {
contentHash = apply(context, contentLoader); // Copy the content
}
// Add the rollback action
final ContentModification original = getOriginalModification(contentHash, backupHash);
final ContentModification rollbackAction = createRollbackEntry(original, contentHash, backupHash);
context.recordChange(original, rollbackAction);
// Fail after adding the undo action
if (! Arrays.equals(contentHash, contentItem.getContentHash()) && failOnContentMismatch(context)) {
throw PatchLogger.ROOT_LOGGER.wrongCopiedContent(contentItem);
}
}
}