/*************************GO-LICENSE-START*********************************
* Copyright 2014 ThoughtWorks, Inc.
*
* 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.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.config.parser;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.PostConstruct;
import com.thoughtworks.go.config.ConfigCache;
import com.thoughtworks.go.config.ConfigCollection;
import com.thoughtworks.go.config.ConfigElementInstantiator;
import com.thoughtworks.go.config.ConfigInterface;
import com.thoughtworks.go.config.ConfigReferenceCollection;
import com.thoughtworks.go.config.ConfigTag;
import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry;
import com.thoughtworks.go.security.GoCipher;
import com.thoughtworks.go.util.ConfigUtil;
import org.jdom2.Element;
import static com.thoughtworks.go.config.ConfigCache.annotationFor;
import static com.thoughtworks.go.config.ConfigCache.isAnnotationPresent;
import static com.thoughtworks.go.config.parser.GoConfigFieldLoader.fieldParser;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
import static com.thoughtworks.go.util.ExceptionUtils.bombIf;
import static com.thoughtworks.go.util.ExceptionUtils.bombIfNull;
import static com.thoughtworks.go.util.ExceptionUtils.bombUnless;
public class GoConfigClassLoader<T> {
private final ConfigUtil configUtil = new ConfigUtil("magic");
private final Element e;
private final Class<T> aClass;
private ConfigCache configCache;
private final GoCipher goCipher;
private ConfigElementImplementationRegistry registry;
private final ConfigReferenceElements configReferenceElements;
@SuppressWarnings("unchecked")
public static <T> GoConfigClassLoader<T> classParser(Element e, Class<T> aClass, ConfigCache configCache, GoCipher goCipher, final ConfigElementImplementationRegistry registry,
ConfigReferenceElements configReferenceElements) {
return new GoConfigClassLoader<>(e, aClass, configCache, goCipher, registry, configReferenceElements);
}
private GoConfigClassLoader(Element e, Class<T> aClass, ConfigCache configCache, GoCipher goCipher, final ConfigElementImplementationRegistry registry,
ConfigReferenceElements configReferenceElements) {
this.e = e;
this.aClass = aClass;
this.configCache = configCache;
this.goCipher = goCipher;
this.registry = registry;
this.configReferenceElements = configReferenceElements;
}
public T parse() {
bombUnless(atElement(),
"Unable to parse element <" + e.getName() + "> for class " + aClass.getSimpleName());
T o = createInstance();
if (isAnnotationPresent(aClass, ConfigReferenceCollection.class)) {
ConfigReferenceCollection referenceCollection = aClass.getAnnotation(ConfigReferenceCollection.class);
String collectionName = referenceCollection.collectionName();
String idFieldName = referenceCollection.idFieldName();
if(e.getAttribute(idFieldName) !=null){
String id = e.getAttribute(idFieldName).getValue();
configReferenceElements.add(collectionName, id, o);
}
}
for (GoConfigFieldLoader field : allFields(o)) {
field.parse();
}
if (isConfigCollection()) {
parseCollection((Collection) o);
}
//check whether there are public PostConstruct methods and call them
postConstruct(o);
return o;
}
private void postConstruct(T o) {
Method[] methods = o.getClass().getMethods();
for (Method method : methods) {
if (isAnnotationPresent(method, PostConstruct.class)) {
try {
method.invoke(o);
} catch (Exception e) {
throw bomb(e);
}
}
}
}
public Collection parseImplicitCollection() {
Collection collection = (Collection) createInstance();
parseCollection(collection);
return collection;
}
@SuppressWarnings("unchecked")
private void parseCollection(Collection collection) {
ConfigCollection collectionAnnotation = annotationFor(aClass, ConfigCollection.class);
Class<?> elementType = collectionAnnotation.value();
for (Element childElement : (List<Element>) e.getChildren()) {
if (isInCollection(childElement, elementType)) {
Class<?> collectionType = findConcreteType(childElement, elementType);
collection.add(classParser(childElement, collectionType, configCache, new GoCipher(), registry, configReferenceElements).parse());
}
}
int minimumSize = collectionAnnotation.minimum();
bombIf(collection.size() < minimumSize,
"Required at least " + minimumSize + " subelements to '" + e.getName() + "'. "
+ "Found " + collection.size() + ".");
}
private <I> List<GoConfigFieldLoader> allFields(I o) {
List<GoConfigFieldLoader> fields = new ArrayList<>();
List<Field> allFields = configCache.getFieldCache().valuesFor(o.getClass());
for (Field field : allFields) {
fields.add(fieldParser(e, o, field, configCache, registry, configReferenceElements));
}
return fields;
}
private boolean atElement() {
ConfigTag annotation = annotationFor(aClass, ConfigTag.class);
String tag = annotation.value();
return configUtil.atTag(e, tag);
}
private T createInstance() {
return ConfigElementInstantiator.instantiateConfigElement(this.goCipher, typeToGenerate(e));
}
@SuppressWarnings("unchecked")
private Class<T> typeToGenerate(Element e) {
if (isImplicitCollection(aClass, configCache)) {
return aClass;
}
Class<T> type = (Class<T>) findConcreteType(e, aClass);
if (type == null) {
bombIfNull(type, String.format("Unable to determine type to generate. Type: %s Element: %s", type.getName(), configUtil.elementOutput(e)));
}
return type;
}
public static boolean compare(Element e, Class<?> implementation, ConfigCache configCache) {
ConfigTag configTag = configTag(implementation, configCache);
return configTag.value().equals(e.getName()) && e.getNamespace().getURI().equals(configTag.namespaceURI());
}
static boolean isImplicitCollection(Class type, ConfigCache configCache) {
return isAnnotationPresent(type, ConfigCollection.class) && !isAnnotationPresent(type, ConfigTag.class);
}
public static ConfigTag configTag(Class<?> type, ConfigCache configCache) {
ConfigTag tag = annotationFor(type, ConfigTag.class);
bombIfNull(tag, "Invalid type '" + type + "' to autoload. Must have ConfigTag annotation.");
return tag;
}
private boolean isConfigCollection() {
return isAnnotationPresent(aClass, ConfigCollection.class);
}
private boolean isInCollection(Element e, Class<?> type) {
return findConcreteType(e, type) != null;
}
private Class<?> findConcreteType(Element e, Class<?> type) {
if (type.isInterface() && isAnnotationPresent(type, ConfigInterface.class)) {
for (Class<?> implementation : registry.implementersOf(type)) {
if (compare(e, implementation, configCache)) {
return implementation;
}
}
} else if (compare(e, type, configCache)) {
return type;
}
return null;
}
}