/* * 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; } } }