View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   * 
28   */
29  package org.openrdf.elmo.sesame;
30  
31  import java.io.IOException;
32  import java.io.Serializable;
33  import java.net.URL;
34  import java.util.Enumeration;
35  import java.util.Map;
36  import java.util.Properties;
37  import java.util.concurrent.ConcurrentHashMap;
38  import java.util.concurrent.ConcurrentMap;
39  
40  import javax.xml.datatype.XMLGregorianCalendar;
41  
42  import org.openrdf.elmo.LiteralManager;
43  import org.openrdf.elmo.annotations.rdf;
44  import org.openrdf.elmo.exceptions.ElmoConversionException;
45  import org.openrdf.elmo.sesame.converters.Marshall;
46  import org.openrdf.elmo.sesame.converters.impl.BigDecimalMarshall;
47  import org.openrdf.elmo.sesame.converters.impl.BigIntegerMarshall;
48  import org.openrdf.elmo.sesame.converters.impl.BooleanMarshall;
49  import org.openrdf.elmo.sesame.converters.impl.ByteMarshall;
50  import org.openrdf.elmo.sesame.converters.impl.CharacterMarshall;
51  import org.openrdf.elmo.sesame.converters.impl.DateMarshall;
52  import org.openrdf.elmo.sesame.converters.impl.DoubleMarshall;
53  import org.openrdf.elmo.sesame.converters.impl.DurationMarshall;
54  import org.openrdf.elmo.sesame.converters.impl.FloatMarshall;
55  import org.openrdf.elmo.sesame.converters.impl.GregorianCalendarMarshall;
56  import org.openrdf.elmo.sesame.converters.impl.IntegerMarshall;
57  import org.openrdf.elmo.sesame.converters.impl.LocaleMarshall;
58  import org.openrdf.elmo.sesame.converters.impl.LongMarshall;
59  import org.openrdf.elmo.sesame.converters.impl.ObjectConstructorMarshall;
60  import org.openrdf.elmo.sesame.converters.impl.ObjectSerializationMarshall;
61  import org.openrdf.elmo.sesame.converters.impl.PatternMarshall;
62  import org.openrdf.elmo.sesame.converters.impl.ShortMarshall;
63  import org.openrdf.elmo.sesame.converters.impl.SqlDateMarshall;
64  import org.openrdf.elmo.sesame.converters.impl.SqlTimeMarshall;
65  import org.openrdf.elmo.sesame.converters.impl.SqlTimestampMarshall;
66  import org.openrdf.elmo.sesame.converters.impl.ValueOfMarshall;
67  import org.openrdf.elmo.sesame.converters.impl.XMLDocumentMarshall;
68  import org.openrdf.elmo.sesame.converters.impl.XMLGregorianCalendarMarshall;
69  import org.openrdf.model.Literal;
70  import org.openrdf.model.URI;
71  import org.openrdf.model.ValueFactory;
72  import org.slf4j.Logger;
73  import org.slf4j.LoggerFactory;
74  
75  /**
76   * Converts between simple Java Objects and Strings.
77   * 
78   * @author James Leigh
79   * 
80   */
81  public class SesameLiteralManager implements LiteralManager<URI, Literal> {
82  
83  	private static final String JAVA_NS = "java:";
84  
85  	private static final String LITERALS_PROPERTIES = "META-INF/org.openrdf.elmo.literals";
86  
87  	private final Logger logger = LoggerFactory.getLogger(SesameLiteralManager.class);
88  
89  	private ClassLoader cl;
90  
91  	private ValueFactory factory;
92  
93  	private ConcurrentMap<URI, Class<?>> javaClasses;
94  
95  	private ConcurrentMap<Class<?>, Marshall<?>> marshalls;
96  
97  	private ConcurrentMap<Class<?>, URI> rdfTypes;
98  
99  	public SesameLiteralManager(ValueFactory factory) {
100 		this.factory = factory;
101 		javaClasses = new ConcurrentHashMap<URI, Class<?>>();
102 		rdfTypes = new ConcurrentHashMap<Class<?>, URI>();
103 		marshalls = new ConcurrentHashMap<Class<?>, Marshall<?>>();
104 	}
105 
106 	public void init() {
107 		if (cl == null)
108 			setClassLoader(Thread.currentThread().getContextClassLoader());
109 	}
110 
111 	public void setClassLoader(ClassLoader cl) {
112 		this.cl = cl;
113 		try {
114 			recordMarshall(new BigDecimalMarshall(factory));
115 			recordMarshall(new BigIntegerMarshall(factory));
116 			recordMarshall(new BooleanMarshall(factory));
117 			recordMarshall(new ByteMarshall(factory));
118 			recordMarshall(new DoubleMarshall(factory));
119 			recordMarshall(new FloatMarshall(factory));
120 			recordMarshall(new IntegerMarshall(factory));
121 			recordMarshall(new LongMarshall(factory));
122 			recordMarshall(new ShortMarshall(factory));
123 			recordMarshall(new CharacterMarshall(factory));
124 			recordMarshall(new DateMarshall(factory));
125 			recordMarshall(new LocaleMarshall(factory));
126 			recordMarshall(new PatternMarshall(factory));
127 			recordMarshall(new DurationMarshall(factory));
128 			recordMarshall(new XMLDocumentMarshall(factory));
129 			XMLGregorianCalendarMarshall xgcm;
130 			xgcm = new XMLGregorianCalendarMarshall(factory);
131 			recordMarshall(xgcm.getJavaClass(), xgcm);
132 			recordMarshall(XMLGregorianCalendar.class, xgcm);
133 			recordMarshall(new GregorianCalendarMarshall(factory));
134 			recordMarshall(new SqlDateMarshall(factory));
135 			recordMarshall(new SqlTimeMarshall(factory));
136 			recordMarshall(new SqlTimestampMarshall(factory));
137 			loadLiterals(SesameLiteralManager.class.getClassLoader());
138 			loadLiterals(cl);
139 		} catch (Exception e) {
140 			throw new ElmoConversionException(e);
141 		}
142 	}
143 
144 	public Class<?> getClass(URI datatype) {
145 		if (javaClasses.containsKey(datatype))
146 			return javaClasses.get(datatype);
147 		try {
148 			if (datatype.getNamespace().equals(JAVA_NS))
149 				return Class.forName(datatype.getLocalName(), true, cl);
150 		} catch (ClassNotFoundException e) {
151 			throw new ElmoConversionException(e);
152 		}
153 		throw new ElmoConversionException("Unknown datatype: " + datatype);
154 	}
155 
156 	public URI getDatatype(Class<?> type) {
157 		if (type.equals(String.class))
158 			return null;
159 		if (rdfTypes.containsKey(type))
160 			return rdfTypes.get(type);
161 		URI datatype = factory.createURI(JAVA_NS, type.getName());
162 		recordType(type, datatype);
163 		return datatype;
164 	}
165 
166 	@SuppressWarnings("unchecked")
167 	public Literal getLiteral(Object object) {
168 		if (object instanceof String)
169 			return factory.createLiteral((String) object);
170 		Marshall marshall = findMarshall(object.getClass());
171 		return marshall.serialize(object);
172 	}
173 
174 	public Literal getLiteral(String value, String language) {
175 		return factory.createLiteral(value, language);
176 	}
177 
178 	@SuppressWarnings("unchecked")
179 	public Object getObject(Literal literal) {
180 		URI datatype = literal.getDatatype();
181 		if (datatype == null)
182 			return literal.getLabel();
183 		Marshall marshall = findMarshall(datatype);
184 		return marshall.deserialize(literal);
185 	}
186 
187 	public void recordMarshall(Class<?> javaClass, Marshall<?> marshall) {
188 		marshalls.put(javaClass, marshall);
189 	}
190 
191 	public void recordType(Class<?> javaClass, String datatype) {
192 		recordType(javaClass, factory.createURI(datatype));
193 	}
194 
195 	public boolean isTypeOfLiteral(Class<?> type) {
196 		return rdfTypes.containsKey(type);
197 	}
198 
199 	private void recordType(Class<?> javaClass, URI datatype) {
200 		if (!javaClasses.containsKey(datatype)) {
201 			javaClasses.putIfAbsent(datatype, javaClass);
202 		}
203 		if (rdfTypes.putIfAbsent(javaClass, datatype) == null) {
204 			Marshall<?> marshall = findMarshall(javaClass);
205 			marshall.setDatatype(datatype);
206 		}
207 	}
208 
209 	@SuppressWarnings("unchecked")
210 	private <T> Marshall<T> findMarshall(Class<T> type) {
211 		if (marshalls.containsKey(type))
212 			return (Marshall<T>) marshalls.get(type);
213 		Marshall<T> marshall;
214 		try {
215 			marshall = new ValueOfMarshall<T>(factory, type);
216 		} catch (NoSuchMethodException e1) {
217 			try {
218 				marshall = new ObjectConstructorMarshall<T>(factory, type);
219 			} catch (NoSuchMethodException e2) {
220 				if (Serializable.class.isAssignableFrom(type)) {
221 					marshall = new ObjectSerializationMarshall<T>(factory, type);
222 				} else {
223 					throw new ElmoConversionException(e1);
224 				}
225 			}
226 		}
227 		Marshall<?> o = marshalls.putIfAbsent(type, marshall);
228 		if (o != null) {
229 			marshall = (Marshall<T>) o;
230 		}
231 		return marshall;
232 	}
233 
234 	private Marshall<?> findMarshall(URI datatype) {
235 		Class<?> type;
236 		if (javaClasses.containsKey(datatype)) {
237 			type = javaClasses.get(datatype);
238 		} else if (datatype.getNamespace().equals(JAVA_NS)) {
239 			try {
240 				type = Class.forName(datatype.getLocalName(), true, cl);
241 			} catch (ClassNotFoundException e) {
242 				throw new ElmoConversionException(e);
243 			}
244 		} else {
245 			throw new ElmoConversionException("Unknown datatype: " + datatype);
246 		}
247 		return findMarshall(type);
248 	}
249 
250 	private void loadLiterals(ClassLoader cl) throws IOException,
251 			ClassNotFoundException {
252 		if (cl == null)
253 			return;
254 		Enumeration<URL> resources = cl.getResources(LITERALS_PROPERTIES);
255 		while (resources.hasMoreElements()) {
256 			URL url = resources.nextElement();
257 			try {
258 				Properties p = new Properties();
259 				p.load(url.openStream());
260 				for (Map.Entry<?, ?> e : p.entrySet()) {
261 					String className = (String) e.getKey();
262 					String types = (String) e.getValue();
263 					Class<?> lc = Class.forName(className, true, cl);
264 					boolean present = lc.isAnnotationPresent(rdf.class);
265 					for (String rdf : types.split("\\s+")) {
266 						if (rdf.length() == 0 && present) {
267 							rdf = lc.getAnnotation(rdf.class).value()[0];
268 							recordType(lc, factory.createURI(rdf));
269 						} else if (rdf.length() == 0) {
270 							logger.warn("Unkown literal mapping {}", className);
271 						} else {
272 							recordType(lc, factory.createURI(rdf));
273 						}
274 					}
275 				}
276 			} catch (IOException e) {
277 				String msg = e.getMessage() + " in: " + url;
278 				IOException ioe = new IOException(msg);
279 				ioe.initCause(e);
280 				throw ioe;
281 			}
282 		}
283 	}
284 
285 	private void recordMarshall(Marshall<?> marshall) {
286 		recordMarshall(marshall.getJavaClass(), marshall);
287 	}
288 
289 }