CustomDatatypeUtil.java
/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.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://license.openmrs.org
*
* 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.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.customdatatype;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.ConceptDatatype;
import org.openmrs.api.APIException;
import org.openmrs.api.context.Context;
import org.openmrs.attribute.Attribute;
import org.openmrs.attribute.AttributeType;
import org.openmrs.serialization.SerializationException;
import org.openmrs.util.OpenmrsConstants;
/**
* Helper methods for dealing with custom datatypes and their handlers
* @since 1.9
*/
public class CustomDatatypeUtil {
private static Log log = LogFactory.getLog(CustomDatatypeUtil.class);
/**
* @param descriptor
* @return a configured datatype appropriate for descriptor
*/
public static CustomDatatype<?> getDatatype(CustomValueDescriptor descriptor) {
return getDatatype(descriptor.getDatatypeClassname(), descriptor.getDatatypeConfig());
}
/**
* @param datatypeClassname
* @param datatypeConfig
* @return a configured datatype with the given classname and configuration
*/
public static CustomDatatype<?> getDatatype(String datatypeClassname, String datatypeConfig) {
try {
Class dtClass = Context.loadClass(datatypeClassname);
CustomDatatype<?> ret = (CustomDatatype<?>) Context.getDatatypeService().getDatatype(dtClass, datatypeConfig);
if (ret == null)
throw new CustomDatatypeException("Can't find datatype: " + datatypeClassname);
return ret;
}
catch (Exception ex) {
throw new CustomDatatypeException("Error loading " + datatypeClassname + " and configuring it with "
+ datatypeConfig, ex);
}
}
/**
* @param descriptor
* @return a configured datatype appropriate for descriptor
*/
public static CustomDatatype<?> getDatatypeOrDefault(CustomValueDescriptor descriptor) {
try {
return getDatatype(descriptor);
}
catch (CustomDatatypeException ex) {
return getDatatype(OpenmrsConstants.DEFAULT_CUSTOM_DATATYPE, null);
}
}
/**
* @param descriptor
* @return a configured datatype handler appropriate for descriptor
*/
public static CustomDatatypeHandler getHandler(CustomValueDescriptor descriptor) {
return getHandler(getDatatypeOrDefault(descriptor), descriptor.getPreferredHandlerClassname(), descriptor
.getHandlerConfig());
}
/**
* @param dt the datatype that this handler should be for
* @param preferredHandlerClassname
* @param handlerConfig
* @return a configured datatype handler with the given classname and configuration
*/
public static CustomDatatypeHandler getHandler(CustomDatatype<?> dt, String preferredHandlerClassname,
String handlerConfig) {
if (preferredHandlerClassname != null) {
try {
Class<? extends CustomDatatypeHandler> clazz = (Class<? extends CustomDatatypeHandler>) Context
.loadClass(preferredHandlerClassname);
CustomDatatypeHandler handler = clazz.newInstance();
if (handlerConfig != null)
handler.setHandlerConfiguration(handlerConfig);
return handler;
}
catch (Exception ex) {
log.warn("Failed to instantiate and configure preferred handler with class " + preferredHandlerClassname
+ " and config " + handlerConfig, ex);
}
}
// if we couldn't get the preferred handler (or none was specified) we get the default one by datatype
return Context.getDatatypeService().getHandler(dt, handlerConfig);
}
/**
* Converts a simple String-based configuration to a serialized form.
* Utility method for {@link AttributeHandler}s that have property-style configuration.
*
* @param simpleConfig
* @return
*/
public static String serializeSimpleConfiguration(Map<String, String> simpleConfig) {
if (simpleConfig == null || simpleConfig.size() == 0)
return "";
try {
return Context.getSerializationService().getDefaultSerializer().serialize(simpleConfig);
}
catch (SerializationException ex) {
throw new APIException(ex);
}
}
/**
* Deserializes a simple String-based configuration from the serialized form used by
* {@link serializeSimpleConfiguration}
* Utility method for {@link AttributeHandler}s that have property-style configuration.
*
* @param serializedConfig
* @return
* @should deserialize a configuration serialized by the corresponding serialize method
*/
@SuppressWarnings("unchecked")
public static Map<String, String> deserializeSimpleConfiguration(String serializedConfig) {
if (StringUtils.isBlank(serializedConfig))
return Collections.emptyMap();
try {
return Context.getSerializationService().getDefaultSerializer().deserialize(serializedConfig, Map.class);
}
catch (SerializationException ex) {
throw new APIException(ex);
}
}
/**
* Uses the appropriate datatypes to convert all values in the input map to their valueReference equivalents.
* This is a convenience method for calling XyzService.getXyz(..., attributeValues, ...).
*
* @param datatypeValues
* @return a map similar to the input parameter, but with typed values converted to their reference equivalents
*/
public static <T extends AttributeType<?>, U> Map<T, String> getValueReferences(Map<T, U> datatypeValues) {
Map<T, String> serializedAttributeValues = null;
if (datatypeValues != null) {
serializedAttributeValues = new HashMap<T, String>();
for (Map.Entry<T, U> e : datatypeValues.entrySet()) {
T vat = e.getKey();
CustomDatatype<U> customDatatype = (CustomDatatype<U>) getDatatype(vat);
String valueReference;
try {
valueReference = customDatatype.getReferenceStringForValue(e.getValue());
}
catch (UnsupportedOperationException ex) {
throw new APIException("Cannot search for attributes with custom datatype: " + customDatatype.getClass());
}
serializedAttributeValues.put(vat, valueReference);
}
}
return serializedAttributeValues;
}
/**
* @return fully-qualified classnames of all registered datatypes
*/
public static List<String> getDatatypeClassnames() {
List<String> ret = new ArrayList<String>();
for (Class<?> c : Context.getDatatypeService().getAllDatatypeClasses())
ret.add(c.getName());
return ret;
}
/**
* @return full-qualified classnames of all registered handlers
*/
public static List<String> getHandlerClassnames() {
List<String> ret = new ArrayList<String>();
for (Class<?> c : Context.getDatatypeService().getAllHandlerClasses())
ret.add(c.getName());
return ret;
}
/**
* @param handler
* @param datatype
* @return whether or not handler is compatible with datatype
*/
public static boolean isCompatibleHandler(CustomDatatypeHandler handler, CustomDatatype<?> datatype) {
List<Class<? extends CustomDatatypeHandler>> handlerClasses = Context.getDatatypeService().getHandlerClasses(
(Class<? extends CustomDatatype<?>>) datatype.getClass());
return handlerClasses.contains(handler.getClass());
}
/**
* To be called by service save methods for customizable implementations.
* Iterates over all attributes and calls save on the {@link ConceptDatatype} for any dirty ones.
*
* @param customizable
*/
public static void saveAttributesIfNecessary(Customizable<?> customizable) {
// TODO decide whether we can move this into a SingleCustomValueSaveHandler instead of leaving it here to be called by each Customizable service's save method
for (Attribute attr : customizable.getAttributes()) {
saveIfDirty(attr);
}
}
/**
* Calls the save method on value's {@link ConceptDatatype} if necessary
*
* @param value
*/
public static void saveIfDirty(SingleCustomValue<?> value) {
if (value.isDirty()) {
CustomDatatype datatype = CustomDatatypeUtil.getDatatype(value.getDescriptor());
if (value.getValue() == null)
throw new InvalidCustomValueException(value.getClass() + " with type=" + value.getDescriptor()
+ " cannot be null");
String existingValueReference = null;
try {
existingValueReference = value.getValueReference();
}
catch (NotYetPersistedException ex) {
// this is expected
}
String newValueReference = datatype.save(value.getValue(), existingValueReference);
value.setValueReferenceInternal(newValueReference);
}
}
/**
* Validates a {@link SingleCustomValue}
*
* @param value
* @return true is value is valid, according to its configured datatype
*/
@SuppressWarnings("unchecked")
public static <T, D extends CustomValueDescriptor> boolean validate(SingleCustomValue<D> value) {
try {
CustomDatatype<T> datatype = (CustomDatatype<T>) getDatatype(value.getDescriptor());
datatype.validate((T) value.getValue());
return true;
}
catch (Exception ex) {
return false;
}
}
}