/*
* Copyright 2013 the original author or authors.
*
* 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.springframework.data.hadoop.config.common.annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
* A base {@link AnnotationBuilder} that allows {@link AnnotationConfigurer}s to be
* applied to it. This makes modifying the {@link AnnotationBuilder} a strategy
* that can be customised and broken up into a number of
* {@link AnnotationConfigurer} objects that have more specific goals than that
* of the {@link AnnotationBuilder}.
* <p>
*
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @param <O> The object that this builder returns
* @param <I> The type of the builder
* @param <B> The type of this builder (that is returned by the base class)
*/
public abstract class AbstractConfiguredAnnotationBuilder<O,I,B extends AnnotationBuilder<O>>
extends AbstractAnnotationBuilder<O> {
private final static Log log = LogFactory.getLog(AbstractConfiguredAnnotationBuilder.class);
/** Configurers which are added to this builder before the configure step */
private final LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>> mainConfigurers =
new LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>>();
/** Configurers which are added to this builder during the configuration phase */
private final LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>> postConfigurers =
new LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>>();
private final Map<Class<Object>, Object> sharedObjects = new HashMap<Class<Object>, Object>();
private final boolean allowConfigurersOfSameType;
/** Current state of this builder */
private BuildState buildState = BuildState.UNBUILT;
private ObjectPostProcessor<Object> objectPostProcessor;
/**
* Instantiates a new annotation builder.
*/
protected AbstractConfiguredAnnotationBuilder() {
this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
}
/**
* Instantiates a new annotation builder.
*
* @param objectPostProcessor the object post processor
*/
protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
this(objectPostProcessor,false);
}
/**
* Instantiates a new annotation builder.
*
* @param objectPostProcessor the object post processor
* @param allowConfigurersOfSameType the allow configurers of same type
*/
protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowConfigurersOfSameType) {
Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}
@Override
protected final O doBuild() throws Exception {
synchronized (mainConfigurers) {
buildState = BuildState.INITIALIZING_MAINS;
beforeInit();
initMainConfigurers();
buildState = BuildState.CONFIGURING_MAINS;
beforeConfigureMains();
configureMainConfigurers();
buildState = BuildState.CONFIGURING_POSTS;
beforeConfigurePosts();
configurePostConfigurers();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
/**
* Similar to {@link #build()} and {@link #getObject()} but checks the state
* to determine if {@link #build()} needs to be called first.
*
* @return the result of {@link #build()} or {@link #getObject()}. If an
* error occurs while building, returns null.
*/
public O getOrBuild() {
if (isUnbuilt()) {
try {
return build();
} catch (Exception e) {
log.error("Failed to perform build. Returning null", e);
return null;
}
} else {
return getObject();
}
}
/**
* Applies a {@link AnnotationConfigurerAdapter} to this
* {@link AnnotationBuilder} and invokes
* {@link AnnotationConfigurerAdapter#setBuilder(AnnotationBuilder)}.
*
* @param configurer the configurer
* @param <C> configurer type
* @return Configurer passed as parameter
* @throws Exception if error occurred
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurerAdapter<O,I,B>> C apply(C configurer) throws Exception {
add(configurer);
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
return configurer;
}
/**
* Similar to {@link #apply(AnnotationConfigurer)} but checks the state
* to determine if {@link #apply(AnnotationConfigurer)} needs to be called first.
*
* @param configurer the configurer
* @param <C> configurer type
* @return Configurer passed as parameter
* @throws Exception if error occurred
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurerAdapter<O,I,B>> C getOrApply(C configurer) throws Exception {
C existing = (C) getConfigurer(configurer.getClass());
if (existing != null) {
return existing;
}
return apply(configurer);
}
/**
* Applies a {@link AnnotationConfigurer} to this {@link AnnotationBuilder}
* overriding any {@link AnnotationConfigurer} of the exact same class. Note
* that object hierarchies are not considered.
*
* @param configurer the configurer
* @param <C> configurer type
* @return Configurer passed as parameter
* @throws Exception if error occurred
*/
public <C extends AnnotationConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
/**
* Sets an object that is shared by multiple {@link AnnotationConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param <C> type
* @param object the Object to store
*/
@SuppressWarnings("unchecked")
public <C> void setSharedObject(Class<C> sharedType, C object) {
this.sharedObjects.put((Class<Object>) sharedType, object);
}
/**
* Gets a shared Object. Note that object hierarchies are not considered.
*
* @param sharedType the type of the shared Object
* @param <C> type
* @return the shared Object or null if it is not found
*/
@SuppressWarnings("unchecked")
public <C> C getSharedObject(Class<C> sharedType) {
return (C) this.sharedObjects.get(sharedType);
}
/**
* Gets the shared objects.
*
* @return Shared objects
*/
public Map<Class<Object>, Object> getSharedObjects() {
return Collections.unmodifiableMap(this.sharedObjects);
}
/**
* Adds {@link AnnotationConfigurer} ensuring that it is allowed and
* invoking {@link AnnotationConfigurer#init(AnnotationBuilder)} immediately
* if necessary.
*
* @param configurer the {@link AnnotationConfigurer} to add
* @throws Exception if an error occurs
*/
@SuppressWarnings("unchecked")
private <C extends AnnotationConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends AnnotationConfigurer<O, B>> clazz =
(Class<? extends AnnotationConfigurer<O, B>>) configurer.getClass();
if (!buildState.isConfigured()) {
synchronized (mainConfigurers) {
List<AnnotationConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.mainConfigurers.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<AnnotationConfigurer<O, B>>(1);
}
configs.add(configurer);
this.mainConfigurers.put(clazz, configs);
if (buildState.isInitializing()) {
configurer.init((B) this);
}
}
} else {
synchronized (postConfigurers) {
List<AnnotationConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.postConfigurers.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<AnnotationConfigurer<O, B>>(1);
}
configs.add(configurer);
this.postConfigurers.put(clazz, configs);
configurer.init((B) this);
}
}
}
/**
* Gets all the {@link AnnotationConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link AnnotationConfigurer} class to look for
* @param <C> configurer type
* @return All configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {
List<C> configs = (List<C>) this.mainConfigurers.get(clazz);
if (configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Removes all the {@link AnnotationConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link AnnotationConfigurer} class to look for
* @param <C> configurer type
* @return Empty list of configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) {
List<C> configs = (List<C>) this.mainConfigurers.remove(clazz);
if (configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Gets the {@link AnnotationConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the configurer class type
* @param <C> configurer type
* @return Matched configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> C getConfigurer(Class<C> clazz) {
List<AnnotationConfigurer<O, B>> configs = this.mainConfigurers.get(clazz);
if (configs == null) {
return null;
}
if (configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Removes and returns the {@link AnnotationConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the configurer class type
* @param <C> configurer type
* @return Matched configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> C removeConfigurer(Class<C> clazz) {
List<AnnotationConfigurer<O, B>> configs = this.mainConfigurers.remove(clazz);
if (configs == null) {
return null;
}
if (configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Specifies the {@link ObjectPostProcessor} to use.
* @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null
* @return the {@link AnnotationBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public O objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
return (O) this;
}
/**
* Performs post processing of an object. The default is to delegate to the
* {@link ObjectPostProcessor}.
*
* @param object the Object to post process
* @param <P> type
* @return the possibly modified Object to use
*/
protected <P> P postProcess(P object) {
return (P) this.objectPostProcessor.postProcess(object);
}
/**
* Invoked prior to invoking each
* {@link AnnotationConfigurer#init(AnnotationBuilder)} method. Subclasses may
* override this method to hook into the lifecycle without using a
* {@link AnnotationConfigurer}.
*
* @throws Exception on error
*/
protected void beforeInit() throws Exception {
}
/**
* Invoked prior to invoking each main
* {@link AnnotationConfigurer#configure(AnnotationBuilder)} method.
* Subclasses may override this method to hook into the lifecycle without
* using a {@link AnnotationConfigurer}.
*
* @throws Exception on error
*/
protected void beforeConfigureMains() throws Exception {
}
/**
* Invoked prior to invoking each post
* {@link AnnotationConfigurer#configure(AnnotationBuilder)} method.
* Subclasses may override this method to hook into the lifecycle without
* using a {@link AnnotationConfigurer}.
*
* @throws Exception on error
*/
protected void beforeConfigurePosts() throws Exception {
}
/**
* Subclasses must implement this method to build the object that is being returned.
*
* @return Object build by this builder
* @throws Exception on error
*/
protected abstract O performBuild() throws Exception;
@SuppressWarnings("unchecked")
private void initMainConfigurers() throws Exception {
for (AnnotationConfigurer<O, B> configurer : getMainConfigurers()) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configureMainConfigurers() throws Exception {
for (AnnotationConfigurer<O, B> configurer : getMainConfigurers()) {
configurer.configure((B) this);
}
}
@SuppressWarnings("unchecked")
private void configurePostConfigurers() throws Exception {
for (AnnotationConfigurer<O, B> configurer : getPostConfigurers()) {
configurer.configure((B) this);
}
}
/**
* Gets all configurers.
*
* @return the configurers
*/
private Collection<AnnotationConfigurer<O, B>> getMainConfigurers() {
List<AnnotationConfigurer<O, B>> result = new ArrayList<AnnotationConfigurer<O, B>>();
for (List<AnnotationConfigurer<O, B>> configs : this.mainConfigurers.values()) {
result.addAll(configs);
}
return result;
}
private Collection<AnnotationConfigurer<O, B>> getPostConfigurers() {
List<AnnotationConfigurer<O, B>> result = new ArrayList<AnnotationConfigurer<O, B>>();
for (List<AnnotationConfigurer<O, B>> configs : this.postConfigurers.values()) {
result.addAll(configs);
}
return result;
}
/**
* Determines if the object is unbuilt.
*
* @return true, if unbuilt else false
*/
private boolean isUnbuilt() {
synchronized (mainConfigurers) {
return buildState == BuildState.UNBUILT;
}
}
/**
* The build state for the application
*/
private static enum BuildState {
/**
* This is the state before the {@link AnnotationBuilder#build()} is invoked
*/
UNBUILT(0),
/**
* The state from when {@link AnnotationBuilder#build()} is first invoked until
* all the {@link AnnotationConfigurer#init(AnnotationBuilder)} methods have
* been invoked.
*/
INITIALIZING_MAINS(1),
/**
* The state from after all main
* {@link AnnotationConfigurer#init(AnnotationBuilder)}
* have been invoked until after all the
* {@link AnnotationConfigurer#configure(AnnotationBuilder)}
* methods have been invoked.
*/
CONFIGURING_MAINS(2),
/**
* The state from after all post
* {@link AnnotationConfigurer#init(AnnotationBuilder)}
* have been invoked until after all the
* {@link AnnotationConfigurer#configure(AnnotationBuilder)}
* methods have been invoked.
*/
CONFIGURING_POSTS(3),
/**
* From the point after all the
* {@link AnnotationConfigurer#configure(AnnotationBuilder)}
* have completed to just after
* {@link AbstractConfiguredAnnotationBuilder#performBuild()}.
*/
BUILDING(4),
/**
* After the object has been completely built.
*/
BUILT(5);
private final int order;
BuildState(int order) {
this.order = order;
}
/**
* Checks if is initializing.
*
* @return true, if is initializing
*/
public boolean isInitializing() {
return INITIALIZING_MAINS.order == order;
}
/**
* Determines if the state is CONFIGURING or later
*
* @return true, if configured
*/
public boolean isConfigured() {
return order >= CONFIGURING_MAINS.order;
}
}
}