package org.etk.orm.api;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Level;
import org.etk.common.logging.Logger;
public abstract class ORMBuilder {
/** . */
private static final Logger log = Logger.getLogger(ORMBuilder.class.getName());
/**
* A special option that will lookup system properties when set to true to configure options by default.
*/
public static final Option<Boolean> USE_SYSTEM_PROPERTIES =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.use_system_properties",
"use system properties");
/**
* The instrumentor class name for ORM's objects. The specified class must implement the
* <tt>org.etk.orm.plugins.instrument.Intrumentor</tt> class.
*/
public static final Option<String> INSTRUMENTOR_CLASSNAME =
new Option<String>(Option.Type.STRING, "org.etk.orm.api.Option.instrumentor.classname", "intrumentor");
/**
* The JCR session life cycle class name. The specified class must implement the
* <tt>org.etk.orm.plugins.jcr.SessionLifeCycle</tt> class.
*/
public static final Option<String> SESSION_LIFECYCLE_CLASSNAME =
new Option<String>(Option.Type.STRING, "org.etk.orm.api.Option.session_lifecycle.classname", "session life cycle");
/**
* The object name formatter class name. The specified class must implement the
* <tt>org.etk.orm.api.format.ObjectFormatter</tt> class.
*/
public static final Option<String> OBJECT_FORMATTER_CLASSNAME =
new Option<String>(Option.Type.STRING, "org.etk.orm.api.Option.object_formatter.classname", "object formatter");
/**
* The boolean indicating if caching is performed. When cache is enabled each session
* maintains a cache that avoids to use the underlying JCR session. As a consequence
* any change made directly to the JCR session will not be visible in the object domain.
*/
public static final Option<Boolean> PROPERTY_CACHE_ENABLED =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.property.cache.enabled",
"property cache enabled");
/**
* Todo.
*/
public static final Option<Boolean> PROPERTY_READ_AHEAD_ENABLED =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.property.read_ahead.enabled",
"property read ahead enabled");
/**
* Enable / disable all JCR optimizations.
*/
public static final Option<Boolean> JCR_OPTIMIZE_ENABLED =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.optimize.jcr.enabled",
"jcr optmisation enabled");
/**
* Enable / disable access to JCR has property.
*/
public static final Option<Boolean> JCR_OPTIMIZE_HAS_PROPERTY_ENABLED =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.optimize.jcr.has_property.enabled",
"jcr has property optimization enabled");
/**
* Enable / disable access to JCR has property.
*/
public static final Option<Boolean> JCR_OPTIMIZE_HAS_NODE_ENABLED =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.optimize.jcr.has_node.enabled",
"jcr has node optimization enabled");
/**
* The path of the root node. The default value is the path of the JCR workspace root node.
*/
public static final Option<String> ROOT_NODE_PATH =
new Option<String>(
Option.Type.STRING,
"org.etk.orm.api.Option.root_node.path",
"the root node path value");
/**
* A boolean option that creates the root node designated by the {@link #ROOT_NODE_PATH} option
* when it does not exist.
*/
public static final Option<Boolean> CREATE_ROOT_NODE =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.root_node.create",
"creates the chromattic root node when it does not exist");
/**
* A boolean option that indicates that the root node should be lazyly created when it is required.
*/
public static final Option<Boolean> LAZY_CREATE_ROOT_NODE =
new Option<Boolean>(
Option.Type.BOOLEAN,
"org.etk.orm.api.Option.root_node.lazy_create",
"when root node is created it is done in a lazy manner");
/**
* A string value that is the root node type when ORM has to build the path to the root node.
*/
public static final Option<String> ROOT_NODE_TYPE =
new Option<String>(
Option.Type.STRING,
"org.etk.orm.api.Option.root_node.root_node_type",
"the root node type when it is created by ORM");
/**
* Create and return an instance of the builder.
*
* @return the ORMBuilder instance
*/
public static ORMBuilder create() {
ServiceLoader<ORMBuilder> loader = ServiceLoader.load(ORMBuilder.class);
Iterator<ORMBuilder> i = loader.iterator();
Throwable throwable = null;
while (i.hasNext()) {
try {
ORMBuilder builder = i.next();
log.debug("Found ORMBuilder implementation " + builder.getClass().getName());
return builder;
}
catch (ServiceConfigurationError error) {
if (throwable == null) {
throwable = error;
}
log.debug("Could not load ORMBuilder implementation, will use next provider", error);
}
}
throw new BuilderException("Could not instanciate builder", throwable);
}
/** The domain classes. */
private final Set<Class<?>> classes;
/** The default configuration. */
private final Configuration config;
/** . */
private boolean initialized;
/** For stuff that need to happen under synchronization. */
private final Object lock = new Object();
public ORMBuilder() {
this.config = createDefaultConfiguration();
this.initialized = false;
this.classes = new HashSet<Class<?>>();
}
/**
* <p>Create the default configuration. Subclass can override it to provide a suitable default configuration.
* The returned object will likely be modified and therefore a new copy should be created every time this
* method is invoked.</p>
*
* <p>The default implementation relies on the {@link ServiceLoader} to load an instance of {@link Configuration.Factory}
* If no configuration is found then a builder exception is thrown.</p>
*
* @return the default configuration
*/
protected Configuration createDefaultConfiguration() {
ServiceLoader loader = ServiceLoader.load(Configuration.Factory.class);
Iterator<Configuration.Factory> i = loader.iterator();
while (i.hasNext()) {
try {
Configuration.Factory factory = i.next();
log.debug("Found ORMBuilder factory implementation " + factory.getClass().getName());
return factory.create();
}
catch (ServiceConfigurationError ignore) {
log.debug("Could not load ORMBuilder factory implementation, will use next provider", ignore);
}
}
throw new BuilderException("Could not instanciate configuration factory");
}
/**
* Returns a copy of the current configuration.
*
* @return the configuration
*/
public final Configuration getConfiguration() {
return new Configuration(config);
}
/**
* Adds a class definition.
*
* @param clazz the class to add
* @throws NullPointerException if the provided class is null
* @throws IllegalStateException if the builder is already initialized
*/
public void add(Class<?> clazz) throws NullPointerException, IllegalStateException {
add(clazz, new Class<?>[0]);
}
/**
* Adds a class definition.
*
* @param first the first class to add
* @param other the other classes to add
* @throws NullPointerException if the provided class is null
* @throws IllegalStateException if the builder is already initialized
*/
public void add(Class<?> first, Class<?>... other) throws NullPointerException, IllegalStateException {
if (first == null) {
throw new NullPointerException();
}
if (other == null) {
throw new NullPointerException();
}
Set<Class<?>> toAdd = new HashSet<Class<?>>(1 + other.length);
toAdd.add(first);
for (Class<?> clazz : other) {
if (clazz == null) {
throw new IllegalArgumentException("No array containing a null class accepted");
}
toAdd.add(clazz);
}
synchronized (lock) {
if (initialized) {
throw new IllegalStateException("Cannot add a class to an initialized builder");
}
classes.addAll(toAdd);
}
}
/**
* Builds the runtime and return a configured {@link org.chromattic.api.Chromattic} instance.
*
* @return the ORM instance
* @throws BuilderException any builder exception
*/
public final ORM build() throws BuilderException {
return build(config);
}
/**
* Builds the runtime and return a configured {@link org.chromattic.api.Chromattic} instance.
*
* @param config the configuration to use
* @return the ORM instance
* @throws BuilderException any builder exception
*/
public final ORM build(Configuration config) throws BuilderException {
// Init if needed
init();
//
return boot(config);
}
/**
* Initialize the builder, this operation should be called once per builder, unlike the {@link #build(Configuration)}
* operation that can be called several times with different configurations. This operation is used to perform the
* initialization that is common to any configuration such as building the meta model from the classes.
*
* @return whether or not initialization occured
* @throws BuilderException any exception that would prevent the initialization to happen correctly
*/
public final boolean init() throws BuilderException {
// Init if needed
synchronized (lock) {
if (!initialized) {
init(classes);
initialized = true;
return true;
} else {
return false;
}
}
}
protected abstract void init(Set<Class<?>> classes) throws BuilderException;
protected abstract ORM boot(Configuration options) throws BuilderException;
/**
* Set the option value.
*
* @param option the option to set
* @param value the option value
* @param <D> the option data type
* @throws NullPointerException if any argument is null
*/
public <D> void setOptionValue(Option<D> option, D value) throws NullPointerException {
config.setOptionValue(option, value, true);
}
/**
* A configuration option.
*
* @param <D> the option data type
*/
public final static class Option<D> {
/**
* The type of an option.
*
* @param <D> the data type
*/
public abstract static class Type<D> {
/** . */
public static final Type<String> STRING = new Type<String>(String.class) {
public String doParse(String value) {
return value;
}
};
/** . */
public static final Type<Boolean> BOOLEAN = new Type<Boolean>(Boolean.class) {
public Boolean doParse(String value) {
return Boolean.valueOf(value);
}
};
/** . */
private final Class<D> javaType;
private Type(Class<D> javaType) {
this.javaType = javaType;
}
public final D parse(String value) {
if (value == null) {
throw new NullPointerException("Cannot parse null value");
}
return doParse(value);
}
/**
* Performs the effective parse, when called the value will never be null.
*
* @param value the value to parse
* @return the parsed value
*/
protected abstract D doParse(String value);
}
/**
* The instance of an option.
*
* @param <D> the data type
*/
public static class Instance<D> {
/** . */
private final Option<D> option;
/** . */
private final D value;
private Instance(Option<D> option, D value) {
if (option == null) {
throw new NullPointerException("No null option accepted");
}
if (value == null) {
throw new NullPointerException("No null option value accepted");
}
this.option = option;
this.value = value;
}
public Option<D> getOption() {
return option;
}
public D getValue() {
return value;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Instance) {
Instance that = (Instance)obj;
return option.name.equals(that.option.name);
}
return false;
}
@Override
public int hashCode() {
return option.name.hashCode();
}
}
/** . */
private final String name;
/** . */
private final String displayName;
/** . */
private final Type<D> type;
private Option(Type<D> type, String name, String displayName) {
this.name = name;
this.displayName = displayName;
this.type = type;
}
public Type<D> getType() {
return type;
}
public String getName() {
return name;
}
public String getDisplayName() {
return displayName;
}
public Option.Instance<D> getInstance(String value) {
D t = type.parse(value);
return t != null ? new Option.Instance<D>(this, t) : null;
}
}
public static class Configuration {
/**
* The configuration factory.
*/
public abstract static class Factory {
/**
* Returns a configuration. The returned object will likely be modified so a new instance should
* be provided on each invocation and no caching should be done.
*
* @return the configuration
*/
public abstract Configuration create();
}
/** . */
protected final Map<String, Option.Instance<?>> entries = new HashMap<String, Option.Instance<?>>();
public Configuration() {
}
/**
* Copy constructor for internal usage.
*
* @param that the options to copy
*/
public Configuration(Configuration that) {
if (that == null) {
throw new NullPointerException("No null configuration accepted");
}
entries.putAll(that.entries);
}
/**
* Returns a configured option instance.
*
* @param name the option name
* @return the corresponding option instance or null
* @throws NullPointerException if the name is null
*/
public Option.Instance<?> getOptionInstance(String name) throws NullPointerException {
if (name == null)
{
throw new NullPointerException();
}
return entries.get(name);
}
/**
* Returns a configured option instance.
*
* @param option the option to return
* @param <D> the option data type
* @return the option instance or null
* @throws NullPointerException if the option is null
*/
public <D> Option.Instance<D> getOptionInstance(Option<D> option) throws NullPointerException {
if (option == null)
{
throw new NullPointerException();
}
@SuppressWarnings("unchecked") // Cast OK
Option.Instance<D> instance = (Option.Instance<D>)entries.get(option.getName());
return instance;
}
/**
* Returns the option value.
*
* @param option the option
* @param <D> the option data type
* @return the option value
* @throws NullPointerException if the option parameter is null
*/
public <D> D getOptionValue(Option<D> option) throws NullPointerException {
Option.Instance<D> instance = getOptionInstance(option);
return instance != null ? instance.value : null;
}
/**
* Set the option value.
*
* @param option the option to set
* @param value the option value
* @param overwrite wheter or not to overwrite an existing value
* @param <D> the option data type
* @return whether or not the value was overwritten
* @throws NullPointerException if any argument is null
*/
public <D> boolean setOptionValue(Option<D> option, D value, boolean overwrite) throws NullPointerException {
if (option == null) {
throw new NullPointerException("No null option");
}
if (value == null) {
throw new NullPointerException("No null value");
}
if (overwrite || entries.get(option.getName()) == null) {
Option.Instance<D> instance = new Option.Instance<D>(option, value);
entries.put(option.getName(), instance);
return true;
} else {
return false;
}
}
}
}