001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.xbean.recipe;
019    
020    import java.util.Collections;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.concurrent.atomic.AtomicLong;
024    import java.lang.reflect.Type;
025    
026    public abstract class AbstractRecipe implements Recipe {
027        private static final AtomicLong ID = new AtomicLong(1);
028        private long id;
029        private String name;
030    
031        protected AbstractRecipe() {
032            id = ID.getAndIncrement();
033        }
034    
035        public String getName() {
036            return name;
037        }
038    
039        public void setName(String name) {
040            if (name == null) throw new NullPointerException("name is null");
041            this.name = name;
042        }
043    
044        public float getPriority() {
045            return 0;
046        }
047    
048        public Object create() throws ConstructionException {
049            return create(null);
050        }
051    
052        public final Object create(ClassLoader classLoader) throws ConstructionException {
053            // if classloader was passed in, set it on the thread
054            ClassLoader oldClassLoader = null;
055            if (classLoader != null) {
056                oldClassLoader = Thread.currentThread().getContextClassLoader();
057                Thread.currentThread().setContextClassLoader(classLoader);
058            }
059    
060            try {
061                return create(Object.class, false);
062            } finally {
063                // if we set a thread context class loader, reset it
064                if (classLoader != null) {
065                    Thread.currentThread().setContextClassLoader(oldClassLoader);
066                }
067            }
068        }
069    
070        public final Object create(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
071            if (expectedType == null) throw new NullPointerException("expectedType is null");
072    
073            // assure there is a valid thread context class loader
074            ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
075            if (oldClassLoader == null) {
076                Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
077            }
078    
079            // if there is no execution context, create one
080            boolean createNewContext = !ExecutionContext.isContextSet();
081            if (createNewContext) {
082                ExecutionContext.setContext(new DefaultExecutionContext());
083            }
084    
085            try {
086                ExecutionContext context = ExecutionContext.getContext();
087    
088                // if this recipe has already been executed in this context, return the currently registered value
089                if (getName() != null && context.containsObject(getName()) && !(context.getObject(getName()) instanceof Recipe)) {
090                    return context.getObject(getName());
091                }
092    
093                // execute the recipe
094                context.push(this);
095                try {
096                    return internalCreate(expectedType, lazyRefAllowed);
097                } finally {
098                    Recipe popped = context.pop();
099                    if (popped != this) {
100                        //noinspection ThrowFromFinallyBlock
101                        throw new IllegalStateException("Internal Error: recipe stack is corrupt:" +
102                                " Expected " + this + " to be popped of the stack but " + popped + " was");
103                    }
104                }
105            } finally {
106                // if we set a new execution context, remove it from the thread
107                if (createNewContext) {
108                    ExecutionContext context = ExecutionContext.getContext();
109                    ExecutionContext.setContext(null);
110    
111                    Map<String,List<Reference>> unresolvedRefs = context.getUnresolvedRefs();
112                    if (!unresolvedRefs.isEmpty()) {
113                        throw new UnresolvedReferencesException(unresolvedRefs);
114                    }
115                }
116    
117                // if we set a thread context class loader, clear it
118                if (oldClassLoader == null) {
119                    Thread.currentThread().setContextClassLoader(null);
120                }
121            }
122        }
123    
124        protected abstract Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException;
125    
126        public List<Recipe> getNestedRecipes() {
127            return Collections.emptyList();
128        }
129    
130        public List<Recipe> getConstructorRecipes() {
131            return Collections.emptyList();
132        }
133    
134        public String toString() {
135            if (name != null) {
136                return name;
137            }
138    
139            String string = getClass().getSimpleName();
140            if (string.endsWith("Recipe")) {
141                string = string.substring(0, string.length() - "Recipe".length());
142            }
143            return string + "@" + id;
144        }
145    }