/*************************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.preprocessor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.List; import com.thoughtworks.go.config.CaseInsensitiveString; import com.thoughtworks.go.config.ConfigAttribute; import com.thoughtworks.go.config.ConfigAttributeValue; import com.thoughtworks.go.config.ConfigCollection; import com.thoughtworks.go.config.ConfigSubtag; import com.thoughtworks.go.config.ConfigValue; import com.thoughtworks.go.config.ParamsConfig; import com.thoughtworks.go.config.ValidationErrorKey; import static com.thoughtworks.go.util.ExceptionUtils.bomb; public class ParamResolver { private final ClassAttributeCache.FieldCache fieldCache; private final ParamHandlerFactory paramHandlerFactory; public ParamResolver(ParamHandlerFactory paramHandlerFactory, ClassAttributeCache.FieldCache fieldCache) { this.paramHandlerFactory = paramHandlerFactory; this.fieldCache = fieldCache; } public <T> void resolve(T resolvable) { ParamResolver resolver = this; if (ParamScope.class.isAssignableFrom(resolvable.getClass())) { ParamScope newScope = (ParamScope) resolvable; resolver = newScope.applyOver(resolver); } resolveStringLeaves(resolvable, resolver); resolveNonStringLeaves(resolvable, resolver); resolveNodes(resolvable, resolver); } public ParamResolver override(ParamsConfig params) { return new ParamResolver(paramHandlerFactory.override(params), fieldCache); } private <T> void resolveNodes(T resolvable, ParamResolver resolver) { resolveCollection(resolvable, resolver); for (Field node : filterResolvables(resolvable, nodeSelectorPredicate())) { try { Object subResolvable = node.get(resolvable); if (subResolvable != null) { resolver.resolve(subResolvable); } } catch (IllegalAccessException e) { bomb(e); } } } private <T> void resolveCollection(Object resolvable, ParamResolver resolver) { if (hasAnnotation(resolvable.getClass(), ConfigCollection.class)) { for (Object subResolvable : (Collection) resolvable) { resolver.resolve(subResolvable); } } } private <T> void resolveNonStringLeaves(T resolvable, ParamResolver resolver) { for (Field leaf : filterResolvables(resolvable, leafAttributeSelectorPredicate())) { try { Object nonStringLeaf = leaf.get(resolvable); if (nonStringLeaf != null) { Class type = leaf.getType(); Field field = getField(leaf, type); field.setAccessible(true); if (field.getType().equals(CaseInsensitiveString.class)) { CaseInsensitiveString cis = (CaseInsensitiveString) field.get(nonStringLeaf); String resolved = resolver.resolveString(resolvable, field, CaseInsensitiveString.str(cis)); CaseInsensitiveString value = new CaseInsensitiveString(resolved); leaf.set(resolvable, type.getConstructor(CaseInsensitiveString.class).newInstance(value)); } else {//assume it is a string, what else can it be? String resolved = resolver.resolveString(resolvable, field, (String) field.get(nonStringLeaf)); leaf.set(resolvable, type.getConstructor(String.class).newInstance(resolved)); } } } catch (Exception e) { bomb(e); } } } private Field getField(Field leaf, Class type) throws NoSuchFieldException { try { return type.getDeclaredField(configAttributeValue(leaf).fieldName()); } catch (NoSuchFieldException e) { Class superclass = type.getSuperclass(); if (superclass == null) { throw e; } return getField(leaf, superclass); } } private <T> void resolveStringLeaves(T resolvable, ParamResolver resolver) { for (Field leaf : filterResolvables(resolvable, leafStringSelectorPredicate())) { try { String preResolved = (String) leaf.get(resolvable); if (preResolved != null) { String resolved = resolver.resolveString(resolvable, leaf, preResolved); leaf.set(resolvable, resolved); } } catch (IllegalAccessException e) { bomb(e); } } } private String resolveString(Object resolvable, Field field, String preResolved) { String fieldName = field.getName(); if (hasValidationErrorKey(field)) { fieldName = validationErrorKey(field).value(); } return new ParamStateMachine().process(preResolved, paramHandlerFactory.createHandler(resolvable, fieldName, preResolved)); } private <T> List<Field> filterResolvables(T resolvable, final NodeSelectorPredicate predicate) { List<Field> interpolatableFields = new ArrayList<>(); for (Field declaredField : getFields(resolvable)) { if (predicate.shouldSelect(declaredField)) { interpolatableFields.add(declaredField); declaredField.setAccessible(true); } } return interpolatableFields; } private List<Field> getFields(Object resolvable) { return fieldCache.valuesFor(resolvable.getClass()); } private NodeSelectorPredicate nodeSelectorPredicate() { return new NodeSelectorPredicate() { public boolean shouldSelect(Field declaredField) { return isConfigSubtag(declaredField) && notSkippable(declaredField); } }; } private boolean isConfigSubtag(Field declaredField) { return hasAnnotation(declaredField, ConfigSubtag.class); } private NodeSelectorPredicate leafStringSelectorPredicate() { return new LeafStringSelectorPredicate(); } private NodeSelectorPredicate leafAttributeSelectorPredicate() { return new LeafAttributeSelectorPredicate(); } private static interface NodeSelectorPredicate { boolean shouldSelect(Field declaredField); } private class LeafStringSelectorPredicate implements NodeSelectorPredicate { public boolean shouldSelect(Field declaredField) { return (isConfigAttribute(declaredField) || isConfigValue(declaredField)) && isString(declaredField) && notSkippable(declaredField); } boolean isString(Field declaredField) { return declaredField.getType().isAssignableFrom(String.class); } } private class LeafAttributeSelectorPredicate implements NodeSelectorPredicate { public boolean shouldSelect(Field declaredField) { return (isConfigAttribute(declaredField) || isConfigValue(declaredField)) && notSkippable(declaredField) && isConfigAttributeValue(declaredField); } private boolean isConfigAttributeValue(Field declaredField) { return configAttributeValue(declaredField) != null; } } private boolean isConfigAttribute(Field declaredField) { return hasAnnotation(declaredField, ConfigAttribute.class); } private boolean notSkippable(Field declaredField) { return !hasAnnotation(declaredField, SkipParameterResolution.class); } private boolean hasValidationErrorKey(Field declaredField) { return hasAnnotation(declaredField, ValidationErrorKey.class); } private boolean isConfigValue(Field declaredField) { return hasAnnotation(declaredField, ConfigValue.class); } private static ConfigAttributeValue configAttributeValue(Field declaredField) { return declaredField.getType().getAnnotation(ConfigAttributeValue.class); } private static ValidationErrorKey validationErrorKey(Field declaredField) { return declaredField.getAnnotation(ValidationErrorKey.class); } private boolean hasAnnotation(AnnotatedElement configElement, Class annotation) { return configElement.isAnnotationPresent(annotation); } }