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 */
018package org.apache.xbean.recipe;
019
020import java.util.Collections;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.atomic.AtomicLong;
024import java.lang.reflect.Type;
025
026public 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}