/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2014
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4che3.conf.api.generic;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.naming.NamingException;
import org.apache.commons.beanutils.PropertyUtils;
import org.dcm4che3.conf.api.ConfigurationException;
import org.dcm4che3.conf.api.DicomConfiguration;
import org.dcm4che3.conf.api.generic.ReflectiveConfig.ConfigReader;
import org.dcm4che3.conf.api.generic.adapters.DefaultConfigTypeAdapters;
import org.dcm4che3.conf.api.generic.adapters.ReflectiveAdapter;
import org.dcm4che3.data.Code;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Generic helper class that provides reflective traversing of classes annotated
* with Config annotations, and storing/reading config from/to such classes
*
* @author Roman K
*
*/
@SuppressWarnings("rawtypes")
public class ReflectiveConfig {
public static final Logger log = LoggerFactory.getLogger(ReflectiveConfig.class);
/**
* Type adapter that handles configuration read/write/serialize/deserialize
* for a specific config java class.
*
* @author Roman K
*
* @param <T>
* Java class
* @param <ST>
* Serialized representation - this intermediate format is used by UIs and storeDiffs.
*/
public interface ConfigTypeAdapter<T, ST> {
/**
* Writes the serialized representation to the configuration backend.
*
* @param obj
* @param config
* ReflectiveConfig that can be used e.g. to retrieve
* DicomConfiguration <b>Can be <i>null</i>!</b>
* @param writer
* ConfigWriter to use
* @param field
* Config field. Can be used to read additional annotations,
* check type, etc.
* @return
*/
void write(ST serialized, ReflectiveConfig config, ConfigWriter writer, Field field) throws ConfigurationException;
/**
* Constructs a serialized representation of an object
*
* @param obj
* @param config
* ReflectiveConfig that can be used e.g. to retrieve
* DicomConfiguration <b>Can be <i>null</i>!</b>
* @param field
* Config field. Can be used to read additional annotations,
* check type, etc.
* @return
* @throws ConfigurationException
*/
ST serialize(T obj, ReflectiveConfig config, Field field) throws ConfigurationException;
/**
* Reads an attribute from configuration into its serialized form
*
* @param str
* @param config
* ReflectiveConfig that can be used e.g. to retrieve
* DicomConfiguration <b>Can be <i>null</i>!</b>
* @param reader
* ConfigReader to use
* @param field
* Config field. Can be used to read additional annotations,
* check type, etc.
* @return
* @throws ConfigurationException
* @throws NamingException
*/
ST read(ReflectiveConfig config, ConfigReader reader, Field field) throws ConfigurationException;
/**
* Constructs an object from its serialized form
*
* @param serialized
* @param config
* ReflectiveConfig that can be used e.g. to retrieve
* DicomConfiguration <b>Can be <i>null</i>!</b>
* @param field
* Config field. Can be used to read additional annotations,
* check type, etc.
* @return
* @throws ConfigurationException
*/
T deserialize(ST serialized, ReflectiveConfig config, Field field) throws ConfigurationException;
void merge(T prev, T curr, ReflectiveConfig config, ConfigWriter diffwriter, Field field) throws ConfigurationException;
/**
* Should indicate whether the specified field will generate a child when writing configuration
* @param field
* @return
*/
boolean isWritingChildren(Field field);
Map<String, Object> getMetadata(ReflectiveConfig config, Field field) throws ConfigurationException;
}
@Deprecated
public interface DiffWriter extends ConfigWriter {
}
/**
* Used by reflective config writer, should implement storage type-specific
* methods
*
* @author Roman K
*/
public interface ConfigWriter {
void storeNotDef(String propName, Object value, String def);
void storeNotEmpty(String propName, Object value);
void storeNotNull(String propName, Object value);
// collections
/**
*
* @param keyName
* @param keyValue
* @param field
* Field with the declared collection/map.
* @return
* @throws ConfigurationException
*/
ConfigWriter getCollectionElementWriter(String keyName, String keyValue, Field field) throws ConfigurationException;
public ConfigWriter createCollectionChild(String propName, Field field) throws ConfigurationException;
void flushWriter() throws ConfigurationException;
void storeDiff(String propName, Object prev, Object curr);
void flushDiffs() throws ConfigurationException;
void removeCollectionElement(String keyName, String keyValue) throws ConfigurationException;
void removeCurrentNode() throws ConfigurationException;
ConfigWriter getCollectionElementDiffWriter(String keyName, String keyValue);
ConfigWriter getChildWriter(String propName, Field field) throws ConfigurationException;
}
/**
* Used by reflective config reader, should implement storage type-specific
* methods
*
* @author Roman K
*/
public interface ConfigReader {
String[] asStringArray(String propName) throws ConfigurationException;
int[] asIntArray(String propName) throws ConfigurationException;
int asInt(String propName, String def) throws ConfigurationException;
String asString(String propName, String def) throws ConfigurationException;
boolean asBoolean(String propName, String def) throws ConfigurationException;
// collections
Map<String, ConfigReader> readCollection(String keyName) throws ConfigurationException;
ConfigReader getChildReader(String propName) throws ConfigurationException;
Code[] asCodeArray(String name) throws ConfigurationException;
}
/*
* Static members
*/
/**
* Default singleton instance for simplified usage
*/
private static final ReflectiveConfig singleton = new ReflectiveConfig(null, null);
/**
* Writes the configuration from the properties of the specified
* configuration object into the config storage using the provided writer.
*
* @param confObj
* Configuration object
* @param writer
* Configuration writer
* @throws ConfigurationException
*/
public static <T> void store(T confObj, ConfigWriter writer) throws ConfigurationException {
singleton.storeConfig(confObj, writer);
}
/**
* Reads the configuration into the properties of the specified
* configuration object using the provided reader.
*
* @param confObj
* @param reader
* @throws ConfigurationException
*/
public static <T> void read(T confObj, ConfigReader reader) throws ConfigurationException {
singleton.readConfig(confObj, reader);
}
/**
* Walk through the <b>from</b> object and for each field annotated with
*
* @ConfigField, copy the value by using getter/setter to the <b>to</b>
* object.
*
* @param from
* @param to
*/
public static <T> void reconfigure(T from, T to) {
// look through all fields of the config class, not including
// superclass fields
for (Field field : from.getClass().getDeclaredFields()) {
// if field is not annotated, skip it
ConfigField fieldAnno = (ConfigField) field.getAnnotation(ConfigField.class);
if (fieldAnno == null)
continue;
try {
PropertyUtils.setSimpleProperty(to, field.getName(), PropertyUtils.getSimpleProperty(from, field.getName()));
} catch (Exception e) {
throw new RuntimeException("Unable to reconfigure a device!", e);
}
}
}
/**
* Calls store diff methods for all pairs of the annotated fields of
* prevConfObj and confObj
*
* @param prevConfObj
* @param confObj
* @param diffWriter
* @throws ConfigurationException
*/
public static <T> void storeAllDiffs(T prevConfObj, T confObj, ConfigWriter diffWriter) throws ConfigurationException {
singleton.storeConfigDiffs(prevConfObj, confObj, diffWriter);
}
/*
* Non-static class members
*/
private Map<Class, ConfigTypeAdapter> customRepresentations;
private DicomConfiguration dicomConfiguration;
/**
* Creates an instance of ReflectiveConfig that will use the specified
* config context and custom representations
*
* @param customRepresentations
* Null can be provided. class-representation map for types that
* should be treated in a special way when reading/writing the
* configuration.
* @param configCtx
* Null can be provided. DicomCofiguration that will be forwarded
* to type adapters as a config context.
*/
public ReflectiveConfig(Map<Class, ConfigTypeAdapter> customRepresentations, DicomConfiguration configCtx) {
super();
this.customRepresentations = customRepresentations;
this.dicomConfiguration = configCtx;
}
/**
* Reads config into the provided confObj from reader
* @param confObj
* @param reader
* @throws ConfigurationException
*/
@SuppressWarnings("unchecked")
public <T> void readConfig(T confObj, ConfigReader reader) throws ConfigurationException {
ReflectiveAdapter<T> adapter = new ReflectiveAdapter<T>((Class<T>) confObj.getClass(), confObj);
adapter.deserialize(adapter.read(this, reader, null), this, null);
}
@SuppressWarnings("unchecked")
public <T> void storeConfig(T confObj, ConfigWriter writer) throws ConfigurationException {
ReflectiveAdapter<T> adapter = new ReflectiveAdapter<T>((Class<T>) confObj.getClass());
adapter.write(adapter.serialize(confObj, this, null), this, writer, null);
}
@SuppressWarnings("unchecked")
public <T> void storeConfigDiffs(T prevConfObj, T confObj, ConfigWriter ldapDiffWriter) throws ConfigurationException {
ReflectiveAdapter<T> adapter = new ReflectiveAdapter<T>((Class<T>) confObj.getClass());
adapter.merge(prevConfObj, confObj, this, ldapDiffWriter, null);
}
@SuppressWarnings("unchecked")
public <T> ConfigTypeAdapter<T, ?> lookupTypeAdapter(Class<T> clazz) {
ConfigTypeAdapter<T, ?> adapter = null;
Map<Class, ConfigTypeAdapter> def = DefaultConfigTypeAdapters.get();
// if it is a config class, use reflective adapter
if (clazz.getAnnotation(ConfigClass.class) != null) {
adapter = new ReflectiveAdapter(clazz);
} else if (clazz.isArray()) {
// if array
adapter = (ConfigTypeAdapter<T, ?>) new DefaultConfigTypeAdapters.ArrayTypeAdapter();
} else {
if (clazz.isEnum()) {
adapter = def.get(Enum.class);
} else {
adapter = def.get(clazz);
}
}
// if still not found, try custom
if (adapter == null && customRepresentations != null)
adapter = customRepresentations.get(clazz);
return adapter;
}
public DicomConfiguration getDicomConfiguration() {
return dicomConfiguration;
}
public void setDicomConfiguration(DicomConfiguration dicomConfiguration) {
this.dicomConfiguration = dicomConfiguration;
}
public void setCustomRepresentations(Map<Class, ConfigTypeAdapter> customRepresentations) {
this.customRepresentations = customRepresentations;
}
}