/*
* Copyright (c) 2007, 2010, James Leigh All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.enilink.komma.literals;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import net.enilink.composition.annotations.Iri;
import net.enilink.composition.properties.exceptions.ObjectConversionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provides;
import net.enilink.vocab.rdf.RDF;
import net.enilink.komma.literals.internal.BigDecimalConverter;
import net.enilink.komma.literals.internal.BigIntegerConverter;
import net.enilink.komma.literals.internal.BooleanConverter;
import net.enilink.komma.literals.internal.ByteConverter;
import net.enilink.komma.literals.internal.CharacterConverter;
import net.enilink.komma.literals.internal.ClassConverter;
import net.enilink.komma.literals.internal.DateConverter;
import net.enilink.komma.literals.internal.DoubleConverter;
import net.enilink.komma.literals.internal.DurationConverter;
import net.enilink.komma.literals.internal.FloatConverter;
import net.enilink.komma.literals.internal.GregorianCalendarConverter;
import net.enilink.komma.literals.internal.IntegerConverter;
import net.enilink.komma.literals.internal.LocaleConverter;
import net.enilink.komma.literals.internal.LongConverter;
import net.enilink.komma.literals.internal.ObjectConstructorConverter;
import net.enilink.komma.literals.internal.ObjectSerializationConverter;
import net.enilink.komma.literals.internal.PatternConverter;
import net.enilink.komma.literals.internal.QNameConverter;
import net.enilink.komma.literals.internal.ShortConverter;
import net.enilink.komma.literals.internal.SqlDateConverter;
import net.enilink.komma.literals.internal.SqlTimeConverter;
import net.enilink.komma.literals.internal.SqlTimestampConverter;
import net.enilink.komma.literals.internal.StringConverter;
import net.enilink.komma.literals.internal.ValueOfConverter;
import net.enilink.komma.literals.internal.XMLGregorianCalendarConverter;
import net.enilink.komma.core.ILiteral;
import net.enilink.komma.core.ILiteralFactory;
import net.enilink.komma.core.URI;
import net.enilink.komma.core.URIs;
/**
* Converts between simple Java Objects and Strings.
*
*/
public class LiteralConverter implements Cloneable {
private static final String DATATYPES_PROPERTIES = "META-INF/net.enilink.komma.datatypes";
private static final String JAVA_SCHEME = "java";
private ClassLoader cl;
private ConcurrentMap<String, IConverter<?>> converters = new ConcurrentHashMap<String, IConverter<?>>();
Injector injector;
private ConcurrentMap<URI, Class<?>> javaClasses = new ConcurrentHashMap<URI, Class<?>>();
@Inject
private ILiteralFactory literalFactory;
private final Logger logger = LoggerFactory
.getLogger(LiteralConverter.class);
private ConcurrentMap<Class<?>, URI> rdfTypes = new ConcurrentHashMap<Class<?>, URI>();
public void addDatatype(Class<?> javaClass, URI datatype) {
recordType(javaClass, datatype);
}
public LiteralConverter clone() {
try {
LiteralConverter cloned = (LiteralConverter) super.clone();
cloned.javaClasses = new ConcurrentHashMap<URI, Class<?>>(
javaClasses);
cloned.converters = new ConcurrentHashMap<String, IConverter<?>>(
converters);
cloned.rdfTypes = new ConcurrentHashMap<Class<?>, URI>(rdfTypes);
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@SuppressWarnings("unchecked")
public ILiteral createLiteral(Object object, URI datatype) {
if (object instanceof String) {
return literalFactory
.createLiteral((String) object, datatype, null);
}
IConverter<Object> converter = null;
if (null != datatype) {
converter = (IConverter<Object>) findConverter(datatype);
} else {
converter = (IConverter<Object>) findConverter(object.getClass());
}
return converter.serialize(object);
}
public Object createObject(ILiteral literal) {
URI datatype = literal.getDatatype();
if (datatype == null) {
return literal.getLabel();
}
IConverter<?> converter = findConverter(datatype);
try {
return converter.deserialize(literal.getLabel());
} catch (Exception e) {
logger.warn("Conversion of literal " + literal + " failed.", e);
return literal;
}
}
public Class<?> findClass(URI datatype) {
if (javaClasses.containsKey(datatype)) {
return javaClasses.get(datatype);
}
try {
if (datatype.scheme().equals(JAVA_SCHEME)) {
return Class.forName(datatype.localPart(), true, cl);
}
} catch (ClassNotFoundException e) {
throw new ObjectConversionException(e);
}
return null;
}
@SuppressWarnings("unchecked")
private <T> IConverter<T> findConverter(Class<T> type) {
String name = type.getName();
if (converters.containsKey(name)) {
return (IConverter<T>) converters.get(name);
}
IConverter<T> converter;
try {
converter = new ValueOfConverter<T>(type);
} catch (NoSuchMethodException e1) {
try {
converter = new ObjectConstructorConverter<T>(type);
} catch (NoSuchMethodException e2) {
if (Serializable.class.isAssignableFrom(type)) {
converter = new ObjectSerializationConverter<T>(type);
} else {
throw new ObjectConversionException(e1);
}
}
}
injector.injectMembers(converter);
IConverter<?> o = converters.putIfAbsent(name, converter);
if (o != null) {
converter = (IConverter<T>) o;
}
return converter;
}
private IConverter<?> findConverter(URI datatype) {
Class<?> type;
if (javaClasses.containsKey(datatype)) {
type = javaClasses.get(datatype);
} else if (datatype.scheme().equals(JAVA_SCHEME)) {
try {
type = Class.forName(datatype.localPart(), true, cl);
} catch (ClassNotFoundException e) {
throw new ObjectConversionException(e);
}
} else {
throw new ObjectConversionException("Unknown datatype: " + datatype);
}
return findConverter(type);
}
public URI findDatatype(Class<?> type) {
if (type.equals(String.class))
return null;
if (rdfTypes.containsKey(type))
return rdfTypes.get(type);
URI datatype = URIs.createURI(JAVA_SCHEME + ":" + type.getName());
recordType(type, datatype);
return datatype;
}
// TODO: check if this two-part test is required
public boolean isDatatype(Class<?> type) {
return rdfTypes.containsKey(type) || null != findConverter(type);
}
private void loadDatatypes(ClassLoader cl, String properties)
throws IOException, ClassNotFoundException {
if (cl == null) {
return;
}
Enumeration<URL> resources = cl.getResources(properties);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
try {
Properties p = new Properties();
p.load(url.openStream());
for (Map.Entry<?, ?> e : p.entrySet()) {
String className = (String) e.getKey();
String types = (String) e.getValue();
Class<?> lc = Class.forName(className, true, cl);
boolean present = lc.isAnnotationPresent(Iri.class);
for (String rdf : types.split("\\s+")) {
if (rdf.length() == 0 && present) {
rdf = lc.getAnnotation(Iri.class).value();
recordType(lc, URIs.createURI(rdf));
} else if (rdf.length() == 0) {
logger.warn("Unkown datatype mapping {}", className);
} else {
recordType(lc, URIs.createURI(rdf));
}
}
}
} catch (IOException e) {
String msg = e.getMessage() + " in: " + url;
IOException ioe = new IOException(msg);
ioe.initCause(e);
throw ioe;
}
}
}
public void recordType(Class<?> javaClass, URI datatype) {
if (!javaClasses.containsKey(datatype)) {
javaClasses.putIfAbsent(datatype, javaClass);
}
if (rdfTypes.putIfAbsent(javaClass, datatype) == null) {
IConverter<?> converter = findConverter(javaClass);
converter.setDatatype(datatype);
}
}
public void registerConverter(Class<?> javaClass, IConverter<?> converter) {
registerConverter(javaClass.getName(), converter);
}
private void registerConverter(IConverter<?> converter) {
registerConverter(converter.getJavaClassName(), converter);
}
public void registerConverter(String javaClassName, IConverter<?> converter) {
injector.injectMembers(converter);
converters.put(javaClassName, converter);
}
@Inject
protected void setClassLoaderAndInjector(final ClassLoader cl,
Injector injector) {
this.cl = cl;
this.injector = injector = injector
.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
}
@Provides
protected DatatypeFactory provideDatatypeFactory()
throws DatatypeConfigurationException {
// workaround for classloading issues w/ factory methods
// http://community.jboss.org/wiki/ModuleCompatibleClassloadingGuide
ClassLoader oldTCCL = Thread.currentThread()
.getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(cl);
return DatatypeFactory.newInstance();
} finally {
Thread.currentThread().setContextClassLoader(
oldTCCL);
}
}
});
try {
registerConverter(new BigDecimalConverter());
registerConverter(new BigIntegerConverter());
registerConverter(new BooleanConverter());
registerConverter(new ByteConverter());
registerConverter(new DoubleConverter());
registerConverter(new FloatConverter());
registerConverter(new IntegerConverter());
registerConverter(new LongConverter());
registerConverter(new ShortConverter());
registerConverter(new CharacterConverter());
registerConverter(new DateConverter());
registerConverter(new LocaleConverter());
registerConverter(new PatternConverter());
registerConverter(new QNameConverter());
registerConverter(new GregorianCalendarConverter());
registerConverter(new SqlDateConverter());
registerConverter(new SqlTimeConverter());
registerConverter(new SqlTimestampConverter());
registerConverter(new ClassConverter());
DurationConverter dm = injector
.getInstance(DurationConverter.class);
registerConverter(dm.getJavaClassName(), dm);
registerConverter(Duration.class, dm);
XMLGregorianCalendarConverter xgcm = injector
.getInstance(XMLGregorianCalendarConverter.class);
registerConverter(xgcm.getJavaClassName(), xgcm);
registerConverter(XMLGregorianCalendar.class, xgcm);
registerConverter(new StringConverter(
"org.codehaus.groovy.runtime.GStringImpl"));
registerConverter(new StringConverter("groovy.lang.GString$1"));
registerConverter(new StringConverter("groovy.lang.GString$2"));
registerConverter(new StringConverter("java.lang.String",
RDF.TYPE_XMLLITERAL));
loadDatatypes(getClass().getClassLoader(), DATATYPES_PROPERTIES);
loadDatatypes(cl, DATATYPES_PROPERTIES);
} catch (Exception e) {
throw new ObjectConversionException(e);
}
}
}