package org.oddjob.values.properties;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.oddjob.Describeable;
import org.oddjob.arooa.ArooaSession;
import org.oddjob.arooa.convert.ArooaConversionException;
import org.oddjob.arooa.parsing.ArooaContext;
import org.oddjob.arooa.runtime.PropertyLookup;
import org.oddjob.arooa.runtime.PropertySource;
/**
* @oddjob.description Creates properties that can used to configure
* other jobs.
* <p>
* There are four ways to set properties:
* <ol>
* <li>As Property name/value Pairs in the values property of this job.</li>
* <li>By defining the environment attribute to be the prefix to which all
* environment variables will be appended as properties.</li>
* <li>By using the sets property to provide a number of addition property
* sets which are likely to be a reference to properties defined elsewhere.</li>
* <li>By defining the Input property to be a File/Resource or some other
* type of input.</li>
* </ol>
* Combinations are possible and the order of evaluation is
* as above. Oddjob will do it's usual property substitution using previously
* defined property values if required.
* <p>
* If the substitute property is true, property values will be evaluated
* for substitution.
* <p>
* The Properties job and {@link PropertiesType} type are very similar, the difference
* between them is that the job defines properties for Oddjob and the type provides
* properties for configuring a single job (which could be the
* sets property of the property job).
* <p>
*
* @oddjob.example
*
* Defining and using a property. Note the escape syntax for property
* expansion.
*
* {@oddjob.xml.resource org/oddjob/values/properties/PropertiesJobFromValues.xml}
*
* @oddjob.example
*
* Defining a property using substitution. This is the same example as
* previously but it is the properties job doing the substitution not
* the Oddjob framework. The value of snack.favourite is escaped because we
* want ${fruit.favourite} passed into the properties job. If the property
* was defined in a file it would not need to be escaped like this.
*
* {@oddjob.xml.resource org/oddjob/values/properties/PropertiesJobWithSubstitution.xml}
*
* @oddjob.example
*
* Loading properties from a class path resource.
*
* {@oddjob.xml.resource org/oddjob/values/properties/PropertiesJobFromInput.xml}
*
* The properties file contains:
*
* {@oddjob.text.resource org/oddjob/values/properties/PropertiesJobTest1.properties}
*
* This will display
* <pre>
* John Smith
* </pre>
*
* @oddjob.example
*
* Overriding Properties. Normally setting a property is first come first
* set. Using the override property on the properties job makes the properties
* defined in that job take priority.
*
* {@oddjob.xml.resource org/oddjob/values/properties/PropertiesJobOverriding.xml}
*
* This will display
* <pre>
* ${fuit.favourite} is apple
* ${fuit.favourite} is apple
* ${fuit.favourite} is banana
* </pre>
*
* @oddjob.example
*
* Capturing Environment Variables. Note that the case sensitivity of
* environment variables is Operating System dependent. On Windows
* <code>${env.Path}</code> and <code>${env.path}</code> would also yield the
* same result. On Unix (generally) only <code>${env.PATH}</code> will work.
*
* {@oddjob.xml.resource org/oddjob/values/properties/PropertiesJobEnvironment.xml}
*/
public class PropertiesJob extends PropertiesJobBase
implements Describeable {
private static final long serialVersionUID = 2014092400L;
/** Delegate to properties base for implementation. (Because we
* can't inherit from it as we do with PropertiesType). */
private transient PropertiesBase delegate;
private transient ArooaSession session;
private volatile transient PropertyLookup lookup;
/** Prefix environment variables. */
private volatile String environment;
/** Flag to indicate setting of system variables. */
private volatile boolean system;
private volatile boolean override;
private transient volatile Strategy strategy;
/**
* Default Constructor.
*/
public PropertiesJob() {
completeConstruction();
}
/**
* Post construction and deserialisation.
*/
private void completeConstruction() {
delegate = new PropertiesBase();
}
@Override
public void setArooaContext(ArooaContext context) {
super.setArooaContext(context);
delegate.setArooaContext(context);
session = ((PropertiesConfigurationSession)
context.getSession()).getOriginal();
}
@Override
protected ArooaSession getArooaSession() {
return session;
}
/**
* Adds the property lookup to the session.
*/
protected void createPropertyLookup() {
super.createPropertyLookup();
if (environment != null) {
lookup = new CompositeLookup(
new EnvVarPropertyLookup(environment),
super.getLookup());
}
}
@Override
protected PropertyLookup getLookup() {
if (lookup == null) {
return super.getLookup();
}
else {
return lookup;
}
}
@Override
protected int execute() throws IOException, ArooaConversionException {
setProperties(delegate.toProperties());
if (system) {
strategy = new SystemStrategy();
}
else {
strategy = new OddjobStrategy();
}
strategy.set();
return 0;
}
@Override
public Map<String, String> describe() {
Properties properties = getProperties();
Map<String, String> description = new TreeMap<String, String>();
if (properties == null) {
return description;
}
PropertyLookup managers = session.getPropertyManager();
Set<String> names = properties.stringPropertyNames();
if (names.isEmpty()) {
for (String name : managers.propertyNames()) {
String value = managers.lookup(name);
PropertySource source = managers.sourceFor(name);
value += " [" + source + "]";
description.put(name, value);
}
}
else {
Strategy strategy = this.strategy;
if (strategy != null) {
strategy.describe(names, description);
}
}
return description;
}
@Override
protected void onReset() {
if (strategy != null) {
strategy.unset();
strategy = null;
}
}
interface Strategy {
void set();
void unset();
void describe(Set<String> names, Map<String, String> description);
}
class OddjobStrategy implements Strategy {
@Override
public void set() {
// All the work's been done during configuration. This is all
// we have left to do.
addPropertyLookup();
}
@Override
public void unset() {
PropertiesJob.super.onReset();
lookup = null;
}
@Override
public void describe(Set<String> names, Map<String, String> description) {
PropertyLookup lookup = getLookup();
if (lookup == null) {
return;
}
PropertyLookup managers = session.getPropertyManager();
for (String name : names) {
String value = lookup.lookup(name);
PropertySource local = lookup.sourceFor(name);
PropertySource actual = managers.sourceFor(name);
if (local != null && !local.equals(actual)) {
value += " *(" + managers.lookup(name) +
") [" + actual + "]";
}
description.put(name, value);
}
}
}
class SystemStrategy implements Strategy {
private volatile SystemPropertyStack.Token token;
@Override
public void set() {
if (token != null) {
throw new IllegalStateException();
}
token = SystemPropertyStack.addProperties(getProperties());
}
@Override
public void unset() {
if (token == null) {
throw new IllegalStateException();
}
SystemPropertyStack.removeProperties(token);
}
@Override
public void describe(Set<String> names, Map<String, String> description) {
PropertyLookup managers = session.getPropertyManager();
for (String name : names) {
String value = System.getProperty(name);
PropertySource local = PropertyLookup.SYSTEM_PROPERTY_SOURCE;
PropertySource actual = managers.sourceFor(name);
if (local != null && !local.equals(actual)) {
value += " *(" + managers.lookup(name) +
") [" + actual + "]";
}
description.put(name, value);
}
}
}
/**
* Getter for environment prefix.
*
* @return The environment prefix. May be null.
*/
public String getEnvironment() {
return environment;
}
/**
* @oddjob.property environment
* @oddjob.description The prefix for environment variables.
* @oddjob.required No.
*/
public void setEnvironment(String environment) {
this.environment = environment;
}
/**
* Getter for system flag.
*
* @return The system flag.
*/
public boolean isSystem() {
return system;
}
/**
* @oddjob.property system
* @oddjob.description Set to true to set System properties rather than
* Oddjob properties.
* @oddjob.required No. Defaults to false
*/
public void setSystem(boolean system) {
this.system = system;
}
/**
* @oddjob.property input
* @oddjob.description An input source for Properties.
* @oddjob.required No.
*/
public void setInput(InputStream input) {
this.delegate.setInput(input);
}
/**
* @oddjob.property fromXML
* @oddjob.description If the input for the properties is in XML format.
* @oddjob.required No, default to false.
*/
public void setFromXML(boolean fromXML) {
this.delegate.setFromXML(fromXML);
}
/**
* Getter for fromXML.
*
* @return true/false.
*/
public boolean isFromXML() {
return this.delegate.isFromXML();
}
/**
* @oddjob.property values
* @oddjob.description Properties defined as key value pairs.
* @oddjob.required No.
*/
public void setValues(String key, String value) {
this.delegate.setValues(key, value);
}
/**
* @oddjob.property sets
* @oddjob.description Extra properties to be merged into the overall
* property set.
* @oddjob.required No.
*/
public void setSets(int index, Properties props) {
this.delegate.setSets(index, props);
}
/**
* Indexed getter for sets.
*
* @param index The index.
* @return The properites.
*/
public Properties getSets(int index) {
return this.delegate.getSets(index);
}
/**
* @oddjob.property substitute
* @oddjob.description Use substitution for the values of ${} type
* properties.
* @oddjob.required No.
*/
public void setSubstitute(boolean substitute) {
this.delegate.setSubstitute(substitute);
}
/**
* Getter for substitute.
*
* @return true/false.
*/
public boolean isSubstitute() {
return delegate.isSubstitute();
}
/**
* Getter for extract.
*
* @return The extract prefix or null.
*/
public String getExtract() {
return delegate.getExtract();
}
/**
* @oddjob.property extract
* @oddjob.description Extract this prefix form property names. Filters
* out properties that do not begin with this prefix.
* @oddjob.required No.
*/
public void setExtract(String extract) {
this.delegate.setExtract(extract);
}
/**
* Getter for prefix.
*
* @return The appending prefix or null.
*/
public String getPrefix() {
return this.delegate.getPrefix();
}
/**
* @oddjob.property prefix
* @oddjob.description Append this prefix to property names.
* @oddjob.required No.
*/
public void setPrefix(String prefix) {
this.delegate.setPrefix(prefix);
}
/**
* Custom serialisation.
*/
private void writeObject(ObjectOutputStream s)
throws IOException {
s.defaultWriteObject();
}
/**
* Custom serialisation.
*/
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
completeConstruction();
}
public boolean isOverride() {
return override;
}
/**
* @oddjob.property override
* @oddjob.description Properties of this job will override any previously
* set.
* @oddjob.required No. Default is false.
*/
public void setOverride(boolean override) {
this.override = override;
}
private class CompositeLookup implements PropertyLookup {
private final PropertySource propertySource = new PropertySource() {
public String toString() {
return PropertiesJob.this.toString();
}
};
private final PropertyLookup environment;
private final PropertyLookup loaded;
CompositeLookup(PropertyLookup first, PropertyLookup second) {
if (first == null) {
throw new NullPointerException("First PropertyLookup null.");
}
if (second == null) {
throw new NullPointerException("Second PropertyLookup null.");
}
this.environment = first;
this.loaded = second;
}
@Override
public String lookup(String propertyName) {
String value = null;
value = environment.lookup(propertyName);
if (value == null) {
value = loaded.lookup(propertyName);
}
return value;
}
@Override
public Set<String> propertyNames() {
Set<String> names = new TreeSet<String>();
names.addAll(environment.propertyNames());
names.addAll(loaded.propertyNames());
return names;
}
@Override
public PropertySource sourceFor(String propertyName) {
if (lookup(propertyName) != null) {
return propertySource;
}
return null;
}
}
}