001/*
002 * **** BEGIN LICENSE BLOCK *****
003 *  Version: MPL 1.1/GPL 2.0/LGPL 2.1
004 *
005 *  The contents of this file are subject to the Mozilla Public License Version
006 *  1.1 (the "License"); you may not use this file except in compliance with
007 *  the License. You may obtain a copy of the License at
008 *  http://www.mozilla.org/MPL/
009 *
010 *  Software distributed under the License is distributed on an "AS IS" basis,
011 *  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012 *  for the specific language governing rights and limitations under the
013 *  License.
014 *
015 *  The Original Code is part of dcm4che, an implementation of DICOM(TM) in
016 *  Java(TM), hosted at https://github.com/gunterze/dcm4che.
017 *
018 *  The Initial Developer of the Original Code is
019 *  Agfa Healthcare.
020 *  Portions created by the Initial Developer are Copyright (C) 2014
021 *  the Initial Developer. All Rights Reserved.
022 *
023 *  Contributor(s):
024 *  See @authors listed below
025 *
026 *  Alternatively, the contents of this file may be used under the terms of
027 *  either the GNU General Public License Version 2 or later (the "GPL"), or
028 *  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
029 *  in which case the provisions of the GPL or the LGPL are applicable instead
030 *  of those above. If you wish to allow use of your version of this file only
031 *  under the terms of either the GPL or the LGPL, and not to allow others to
032 *  use your version of this file under the terms of the MPL, indicate your
033 *  decision by deleting the provisions above and replace them with the notice
034 *  and other provisions required by the GPL or the LGPL. If you do not delete
035 *  the provisions above, a recipient may use your version of this file under
036 *  the terms of any one of the MPL, the GPL or the LGPL.
037 *
038 *  ***** END LICENSE BLOCK *****
039 */
040package org.dcm4che3.conf.core.api.internal;
041
042
043import org.apache.commons.beanutils.PropertyUtils;
044import org.dcm4che3.conf.core.api.*;
045
046import java.lang.annotation.Annotation;
047import java.lang.reflect.Field;
048import java.lang.reflect.InvocationTargetException;
049import java.lang.reflect.Type;
050import java.util.*;
051
052/**
053 * This class shall NOT be referenced externally, it will be removed/renamed/refactored without notice.
054 * Caches to minimize reflection access.
055 *
056 * @author Roman K
057 */
058public class ConfigReflection {
059
060
061    private static final Map<Class, ClassInfo> classInfoCache = Collections.synchronizedMap(new HashMap<Class, ClassInfo>());
062    private static final Map<Class, Boolean> isClassConfigurable = Collections.synchronizedMap(new HashMap<Class, Boolean>());
063
064    private static final Map<Class, ConfigProperty> dummyPropsCache = Collections.synchronizedMap(new HashMap<Class, ConfigProperty>());
065
066    public static List<ConfigProperty> getAllConfigurableFields(Class clazz) {
067        return getClassInfo(clazz).configurableProperties;
068    }
069
070    public static ConfigProperty getDummyPropertyForClass(Class clazz) {
071        ConfigProperty found = dummyPropsCache.get(clazz);
072
073        if (found != null) {
074            return found;
075        } else {
076            ConfigProperty property = new ConfigProperty(clazz);
077            dummyPropsCache.put(clazz, property);
078            return property;
079        }
080    }
081
082    private static ClassInfo getClassInfo(Class clazz) {
083        ClassInfo classInfo = classInfoCache.get(clazz);
084
085        if (classInfo != null) {
086            return classInfo;
087        } else {
088            return processAndCacheClassInfo(clazz);
089        }
090    }
091
092    public static boolean isConfigurableClass(Class clazz) {
093
094        if (isClassConfigurable.containsKey(clazz))
095            return isClassConfigurable.get(clazz);
096
097        boolean isItForReal = clazz.getAnnotation(ConfigurableClass.class) != null;
098        isClassConfigurable.put(clazz, isItForReal);
099
100        return isItForReal;
101    }
102
103    public static ConfigProperty getUUIDPropertyForClass(Class clazz) {
104        return getClassInfo(clazz).uuidProperty;
105    }
106
107
108    private static ClassInfo processAndCacheClassInfo(Class clazz) {
109
110        ConfigurableClass configClassAnno = (ConfigurableClass) clazz.getAnnotation(ConfigurableClass.class);
111        if (configClassAnno == null)
112            throw new IllegalArgumentException("Class '" + clazz.getName() + "' is not a configurable class. Make sure the a dependency to org.dcm4che.conf.core-api exists.");
113
114
115        ClassInfo classInfo = scanClass(clazz);
116
117        // some restrictions on extensions
118        if (ConfigurableClassExtension.class.isAssignableFrom(clazz)) {
119
120            if (configClassAnno.referable()) {
121                throw new IllegalArgumentException("A configurable extension class MUST NOT be referable - violated by class " + clazz.getName());
122            }
123
124            if (classInfo.uuidProperty != null) {
125                throw new IllegalArgumentException("A configurable extension class MUST NOT have a uuid - violated by class " + clazz.getName());
126            }
127        }
128
129        classInfoCache.put(clazz, classInfo);
130
131        return classInfo;
132    }
133
134
135    private static ClassInfo scanClass(Class clazz) {
136
137        ClassInfo classInfo = new ClassInfo();
138        classInfo.configurableProperties = new ArrayList<ConfigProperty>();
139
140        // scan all fields from this class and superclasses
141        for (Field field : getAllFields(clazz)) {
142            if (field.getAnnotation(ConfigurableProperty.class) != null) {
143
144                ConfigProperty ap = new ConfigProperty(
145                        annotationsArrayToMap(field.getAnnotations()),
146                        field.getName(),
147                        field.getGenericType()
148                );
149                classInfo.configurableProperties.add(ap);
150
151                if (ap.isUuid()) {
152                    if (classInfo.uuidProperty != null) {
153                        throw new IllegalArgumentException("A configurable class MUST NOT have more than one UUID field - violated by class " + clazz.getName());
154                    }
155
156                    classInfo.uuidProperty = ap;
157                }
158
159                if (ap.isOlockHash()) {
160                    if (classInfo.olockHashProperty != null) {
161                        throw new IllegalArgumentException("A configurable class MUST NOT have more than one optimistic locking hash field - violated by class " + clazz.getName());
162                    }
163
164                    classInfo.olockHashProperty = ap;
165                }
166            } else if (field.getAnnotation(Parent.class) != null) {
167                if (classInfo.parentField != null) {
168                    throw new IllegalArgumentException("A configurable class MUST NOT have more than one field annotated with @Parent - violated by class " + clazz.getName());
169                }
170
171                classInfo.parentField = field;
172            }
173        }
174
175
176        if (!ConfigurableClassExtension.class.isAssignableFrom(clazz) && classInfo.uuidProperty == null && classInfo.parentField != null) {
177            throw new IllegalArgumentException("A configurable class that refers to a @Parent must have a uuid property defined (except the extensions). Violated by " + clazz.getName());
178        }
179
180        return classInfo;
181    }
182
183    public static Map<Type, Annotation> annotationsArrayToMap(Annotation[] annos) {
184        HashMap<Type, Annotation> annotations = new HashMap<Type, Annotation>();
185        for (Annotation anno : annos)
186            annotations.put(anno.annotationType(), anno);
187        return annotations;
188    }
189
190    public static Field getParentPropertyForClass(Class<?> extensionClass) {
191        return getClassInfo(extensionClass).parentField;
192    }
193
194
195    /**
196     * Gets all the fields for the class and it's superclass(es)
197     */
198    private static List<Field> getAllFields(Class clazz) {
199
200        List<Field> fields = new ArrayList<Field>();
201
202        // get all fields of the current class (includes public, protected, default, and private fields)
203        fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
204
205        // go to the parent recursively
206        Class<?> parent = clazz.getSuperclass();
207        if (parent != null)
208            fields.addAll(getAllFields(parent));
209
210        return fields;
211    }
212
213    public static void setProperty(Object object, ConfigProperty property, Object value) throws ReflectionAccessException {
214        setProperty(object, property.getName(), value);
215    }
216
217    public static void setProperty(Object object, String propertyName, Object value) throws ReflectionAccessException {
218        try {
219            PropertyUtils.setSimpleProperty(object, propertyName, value);
220        } catch (IllegalAccessException e) {
221            throw new ReflectionAccessException("Could not set property " + propertyName + " in class " + object.getClass().toString(), e);
222        } catch (InvocationTargetException e) {
223            throw new ReflectionAccessException("Could not set property " + propertyName + " in class " + object.getClass().toString(), e);
224        } catch (NoSuchMethodException e) {
225            throw new ReflectionAccessException("Could not set property " + propertyName + " in class " + object.getClass().toString(), e);
226        }
227    }
228
229    public static Object getProperty(Object object, ConfigProperty property) throws ReflectionAccessException {
230        try {
231            return PropertyUtils.getSimpleProperty(object, property.getName());
232        } catch (IllegalAccessException e) {
233            throw new ReflectionAccessException("Could not get property " + property + " in class " + object.getClass().toString(), e);
234        } catch (InvocationTargetException e) {
235            throw new ReflectionAccessException("Could not get property " + property + " in class " + object.getClass().toString(), e);
236        } catch (NoSuchMethodException e) {
237            throw new ReflectionAccessException("Could not get property " + property + " in class " + object.getClass().toString(), e);
238        }
239    }
240
241    private static class ClassInfo {
242
243        List<ConfigProperty> configurableProperties;
244        ConfigProperty uuidProperty;
245        ConfigProperty olockHashProperty;
246        Field parentField;
247
248    }
249}