package net.sourceforge.cruisecontrol.builders;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import net.sourceforge.cruisecontrol.CruiseControlException;
import net.sourceforge.cruisecontrol.Progress;
import net.sourceforge.cruisecontrol.util.GZippedStdoutBuffer;
import net.sourceforge.cruisecontrol.util.StdoutBuffer;
import net.sourceforge.cruisecontrol.util.ValidationHelper;
import org.apache.log4j.Logger;
import org.jdom.Element;
/**
* The implementation of {@link PipedScript} handling all the boring cases.
* @author dtihelka
*/
public abstract class PipedScriptBase implements PipedScript {
/** The ID of the script set by {@link #setID(String)}. */
private String id = null;
/** The ID of script to pipe from, set by {@link #setPipeFrom(String)}. */
private String pipeFrom = null;
/** The index of script to wait for, set by {@link #setWaitFor(String)}. */
private String waitFor = null;
/** The value set by {@link #setRepipe(String)} */
private String repipe = null;
/** The value set by {@link #setDisable(boolean)} */
private boolean disable = false;
/** Signalizes wherever the script finished or not */
private boolean isDone = false;
/** Keep STDOUT gzipped? Set by {@link #setGZipStdout(boolean)}. */
private Boolean gzip = null;
/** Is STDOUT of the script binary? Set by {@link #setBinaryOutput(boolean)} */
private Boolean binary = null;
/** The buffer holding the output of the command */
private transient StdoutBuffer outputBuffer = null;
/** The stream to read STDIN of the command, set by {@link #setInputProvider(InputStream)}. */
private transient InputStream inputProvider = null;
/** The build properties, set by {@link #setBuildProperties(Map)}. */
private transient Map<String, String> buildProperties = null;
/** The callback to provide progress updates, set by {@link #setProgress(Progress)}. */
private transient Progress progress = null;
/** The parent element into with the build log (created by {@link #build()} method) is stored. */
private transient Element buildLogParent;
/**
* Execute the script and return the results as XML. The script may optionally be fed up
* by data read from input, and its STDOUT may optionally be stored in for later use.
*
* Use the variables/methods of the object to get the required data. The method is called
* from {@link #run()}.
*
* @return the XML element with the script run information.
* @throws CruiseControlException when the build fails
*/
protected abstract Element build() throws CruiseControlException;
/**
* @return the instance of Logger
*/
protected abstract Logger log();
/** The implementation of {@link PipedScript#validate()}, it checks if all the required
* items are set.
* Do not forget to call this method in the overridden classes!
*
* @throws CruiseControlException if the validation fails */
@Override
public void validate() throws CruiseControlException {
ValidationHelper.assertIsSet(id, "ID", getClass());
}
/** The implementation of {@link PipedScript#initialize()}. Do not forget to call this method
* in the overridden classes!
*/
@Override
public void initialize() {
if (Boolean.TRUE.equals(gzip)) {
outputBuffer = new GZippedStdoutBuffer(log());
} else {
outputBuffer = new StdoutBuffer(log());
}
if (buildProperties == null) {
log().warn("Build properties has not been set, setting empty map");
buildProperties = new HashMap<String, String>();
}
if (buildLogParent == null) {
log().warn("Build log parent has not been set, setting dummy element");
buildLogParent = new Element("DummyLogParent");
}
// No done from now on
this.isDone = false;
}
/**
* Main build caller. It calls {@link #build()} implementation and then cleares
* both pipe streams and sets {@link #isDone} to return <code>true</code>
*/
@Override
public final void run() {
try {
Element buildLog;
/* Start and store the build log element created */
log().info("Script ID '" + this.getID() + "' started");
buildLog = build();
/* Add the element into the parent */
synchronized (buildLogParent) {
this.buildLogParent.addContent(buildLog.detach());
}
log().info("Script ID '" + this.getID() + "' finished");
} catch (Throwable e) {
log().error("Script ID '" + this.getID() + "' failed", e);
} finally {
/* Close the buffer to signalize that all has been written, and clear STDIN
* provider to signalize to GC that it is not longer needed */
this.outputBuffer.close();
this.inputProvider = null;
this.buildLogParent = null;
this.isDone = true;
}
}
// /** The implementation of {@link PipedScript#clean()}, it cleans all large-memory consuming
// * objects hold. Do not forget to call this method in the overridden classes!
// */
// @Override
// public void clean() {
// // Clean those since they contain the largest amount of memory
// this.outputBuffer = null;
// this.buildLogParent = null;
// }
@Override
public void setID(String value) {
this.id = value;
}
@Override
public String getID() {
return this.id;
}
@Override
public void setPipeFrom(String value) {
this.pipeFrom = value;
}
@Override
public String getPipeFrom() {
return this.pipeFrom;
}
@Override
public void setWaitFor(String value) {
this.waitFor = value;
}
@Override
public String getWaitFor() {
return this.waitFor;
}
@Override
public void setRepipe(String repipe) {
this.repipe = repipe;
}
@Override
public String getRepipe() {
return this.repipe;
}
@Override
public void setDisable(boolean disable) {
this.disable = disable;
}
@Override
public boolean getDisable() {
return this.disable;
}
@Override
public void setBuildLogParent(Element buildLogParent) {
this.buildLogParent = buildLogParent;
}
@Override
public void setBuildProperties(Map<String, String> buildProperties) {
this.buildProperties = buildProperties; /* Shallow copy should be OK */
}
/**
* @return the instance set through {@link #setBuildProperties(Map)} or empty map when no
* instance has been set
*/
protected Map<String, String> getBuildProperties() {
return this.buildProperties;
}
@Override
public void setProgress(Progress progress) {
this.progress = progress;
}
/**
* @return the instance set through {@link #setProgress(Progress)} or <code>null</code> if no
* instance has been set through {@link #setProgress(Progress)}
*/
protected Progress getProgress() {
return this.progress; // null can be returned
}
@Override
public void setInputProvider(InputStream stdinProvider) {
this.inputProvider = stdinProvider;
}
/**
* @return the instance set through {@link #setInputProvider(InputStream)} or <code>null</code>
* if no provider has been set through {@link #setInputProvider(InputStream)}
*/
protected InputStream getInputProvider() {
return this.inputProvider; // null can be returned
}
/**
* @return the stream to which the output of the script is supposed to be written.
* @throws NullPointerException when {@link #initialize()} has not been called.
*/
protected OutputStream getOutputBuffer() {
if (this.outputBuffer == null || this.isDone) {
throw new NullPointerException("Object has not been initialized");
}
return this.outputBuffer; // null can be returned
}
@Override
public InputStream getOutputReader() {
if (this.outputBuffer == null) {
throw new NullPointerException("Object has not been initialized");
}
try {
return this.outputBuffer.getContent();
} catch (IOException e) {
log().error("exec ID=" + getID() + ": unable to create STDOUT reader", e);
return new ByteArrayInputStream(new byte[0]);
}
}
@Override
public boolean isDone() {
/* Does not have to be synchronized, true cannot be returned when the build() is
* still running ... */
return this.isDone;
}
@Override
public void setGZipStdout(boolean gzip) {
this.gzip = Boolean.valueOf(gzip);
}
@Override
public Boolean getGZipStdout() {
return this.gzip;
}
@Override
public void setBinaryOutput(boolean binary) {
this.binary = Boolean.valueOf(binary);
}
@Override
public Boolean getBinaryOutput() {
return this.binary;
}
}