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 static org.openrdf.query.QueryLanguage.SERQL;
32  import info.aduna.iteration.CloseableIteration;
33  
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.HashSet;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Set;
42  
43  import javax.xml.XMLConstants;
44  import javax.xml.namespace.QName;
45  
46  import org.openrdf.elmo.ResourceManager;
47  import org.openrdf.elmo.RoleMapper;
48  import org.openrdf.elmo.annotations.disjointWith;
49  import org.openrdf.elmo.exceptions.ElmoIOException;
50  import org.openrdf.elmo.exceptions.ElmoPersistException;
51  import org.openrdf.elmo.sesame.iterators.ElmoIteration;
52  import org.openrdf.model.Namespace;
53  import org.openrdf.model.Resource;
54  import org.openrdf.model.Statement;
55  import org.openrdf.model.URI;
56  import org.openrdf.model.Value;
57  import org.openrdf.model.ValueFactory;
58  import org.openrdf.model.vocabulary.RDF;
59  import org.openrdf.query.Binding;
60  import org.openrdf.query.BindingSet;
61  import org.openrdf.query.MalformedQueryException;
62  import org.openrdf.query.QueryEvaluationException;
63  import org.openrdf.query.TupleQuery;
64  import org.openrdf.query.TupleQueryResult;
65  import org.openrdf.repository.RepositoryException;
66  import org.openrdf.repository.RepositoryResult;
67  import org.openrdf.repository.contextaware.ContextAwareConnection;
68  
69  /**
70   * Determine the rdf:types of a Sesame Resource.
71   * 
72   * @author James Leigh
73   * 
74   */
75  public class SesameResourceManager implements ResourceManager<Resource> {
76  
77  	private static final String DEFAULT_PREFIX = XMLConstants.DEFAULT_NS_PREFIX;
78  
79  	private ContextAwareConnection conn;
80  
81  	private ValueFactory vf;
82  
83  	private RoleMapper<URI> mapper;
84  
85  	public void setConnection(ContextAwareConnection conn) {
86  		this.conn = conn;
87  	}
88  
89  	public void setRoleMapper(RoleMapper<URI> mapper) {
90  		this.mapper = mapper;
91  	}
92  
93  	public void setValueFactory(ValueFactory vf) {
94  		this.vf = vf;
95  	}
96  
97  	public Resource createResource(QName qname) {
98  		if (qname == null)
99  			return vf.createBNode();
100 		String ns = qname.getNamespaceURI();
101 		String name = qname.getLocalPart();
102 		if (ns.equals(XMLConstants.NULL_NS_URI)) {
103 			String prefix = qname.getPrefix();
104 			if (prefix.equals(DEFAULT_PREFIX))
105 				return vf.createURI(name);
106 			try {
107 				ns = conn.getNamespace(prefix);
108 				return vf.createURI(ns, name);
109 			} catch (RepositoryException e) {
110 				throw new ElmoIOException(e);
111 			}
112 		}
113 		return vf.createURI(ns, name);
114 	}
115 
116 	public QName createQName(Resource res) {
117 		if (res instanceof URI) {
118 			URI uri = (URI) res;
119 			String prefix = getPrefix(uri.getNamespace());
120 			return new QName(uri.getNamespace(), uri.getLocalName(), prefix);
121 		}
122 		return null;
123 	}
124 
125 	public Iterator<Resource> createRoleQuery(Class<?> concept) {
126 		StringBuilder query = new StringBuilder();
127 		query.append("SELECT DISTINCT subj FROM");
128 		Collection<URI> types = new HashSet<URI>();
129 		mapper.findSubTypes(concept, types);
130 		Iterator<URI> iter = types.iterator();
131 		while (iter.hasNext()) {
132 			query.append(" {subj} rdf:type {<");
133 			query.append(iter.next()).append(">}");
134 			if (iter.hasNext())
135 				query.append(" UNION SELECT subj FROM");
136 		}
137 		try {
138 			TupleQuery q = conn.prepareTupleQueryWithinContext(SERQL, query
139 					.toString(), null);
140 			TupleQueryResult result;
141 			result = q.evaluate();
142 			final List<String> bindings = result.getBindingNames();
143 			return new ElmoIteration<BindingSet, Resource>(result) {
144 				@Override
145 				protected Resource convert(BindingSet sol) {
146 					String b0 = bindings.get(0);
147 					Binding binding = sol.getBinding(b0);
148 					assert binding.getValue() instanceof Resource : binding
149 							.getValue();
150 					return (Resource) binding.getValue();
151 				}
152 			};
153 		} catch (QueryEvaluationException e) {
154 			throw new ElmoIOException(e);
155 		} catch (MalformedQueryException e) {
156 			throw new ElmoIOException(e);
157 		} catch (RepositoryException e) {
158 			throw new ElmoIOException(e);
159 		}
160 	}
161 
162 	@SuppressWarnings("unchecked")
163 	public Class<?>[] getRoles(Resource res) {
164 		CloseableIteration<? extends Statement, RepositoryException> stmts = null;
165 		try {
166 			try {
167 				stmts = conn.getStatements(res, RDF.TYPE, null);
168 				List<Class<?>> roles = null;
169 				if (res instanceof URI
170 						&& mapper.isIndividualRolesPresent((URI) res)) {
171 					roles = new ArrayList<Class<?>>();
172 					mapper.findIndividualRoles((URI) res, roles);
173 				}
174 				if (stmts.hasNext()) {
175 					Value obj = stmts.next().getObject();
176 					assert obj instanceof URI : obj;
177 					if (roles == null && !stmts.hasNext())
178 						return mapper.findRoles((URI) obj);
179 					List<URI> types = new ArrayList<URI>();
180 					types.add((URI) obj);
181 					while (stmts.hasNext()) {
182 						obj = stmts.next().getObject();
183 						assert obj instanceof URI;
184 						types.add((URI) obj);
185 					}
186 					if (roles == null) {
187 						roles = new ArrayList<Class<?>>();
188 					}
189 					mapper.findRoles(types, roles);
190 				} else if (roles == null) {
191 					return mapper.findBaseRoles();
192 				} else {
193 					mapper.findRoles(Collections.EMPTY_SET, roles);
194 				}
195 				return roles.toArray(new Class<?>[roles.size()]);
196 			} finally {
197 				if (stmts != null)
198 					stmts.close();
199 			}
200 		} catch (RepositoryException e) {
201 			throw new ElmoIOException(e);
202 		}
203 	}
204 
205 	public Class<?>[] mergeRole(Resource resource, Class<?> role) {
206 		try {
207 			Class<?>[] currently = getRoles(resource);
208 			for (int i = 0; i < currently.length; i++) {
209 				if (role.equals(currently[i]))
210 					return currently;
211 			}
212 			if (role.isInterface()) {
213 				URI type = mapper.findType(role);
214 				if (type == null)
215 					throw new ElmoPersistException("Concept not registered: "
216 							+ role.getSimpleName());
217 				conn.add(resource, RDF.TYPE, type);
218 				if (currently.length == 0)
219 					return mapper.findRoles(type);
220 				Set<Class<?>> roles = new HashSet<Class<?>>();
221 				roles.addAll(Arrays.asList(currently));
222 				roles.addAll(Arrays.asList(mapper.findRoles(type)));
223 				assert isDisjointWith(role, roles);
224 				return roles.toArray(new Class<?>[roles.size()]);
225 			}
226 			Collection<URI> types = mapper.findTypes(role, new HashSet<URI>());
227 			assert types.size() > 0 : "Role not registered";
228 			for (URI type : types) {
229 				conn.add(resource, RDF.TYPE, type);
230 			}
231 			if (types.size() == 1 && currently.length == 0)
232 				return mapper.findRoles(types.iterator().next());
233 			Set<Class<?>> roles = new HashSet<Class<?>>();
234 			if (currently.length > 0)
235 				roles.addAll(Arrays.asList(currently));
236 			mapper.findRoles(types, roles);
237 			assert isDisjointWith(role, roles);
238 			return roles.toArray(new Class<?>[roles.size()]);
239 		} catch (RepositoryException e) {
240 			throw new ElmoPersistException(e);
241 		}
242 	}
243 
244 	public Class<?>[] removeRole(Resource resource, Class<?> role) {
245 		try {
246 			URI type = mapper.findType(role);
247 			if (type == null)
248 				throw new ElmoPersistException("Concept not registered: "
249 						+ role.getSimpleName());
250 			conn.remove(resource, RDF.TYPE, type);
251 			return getRoles(resource);
252 		} catch (RepositoryException e) {
253 			throw new ElmoPersistException(e);
254 		}
255 	}
256 
257 	public void removeResource(Resource resource) {
258 		try {
259 			boolean autoCommit = conn.isAutoCommit();
260 			conn.setAutoCommit(false);
261 			conn.remove(resource, (URI) null, null);
262 			conn.remove(null, (URI) null, resource);
263 			conn.setAutoCommit(autoCommit);
264 		} catch (Exception e) {
265 			throw new ElmoPersistException(e);
266 		}
267 	}
268 
269 	public void renameResource(Resource before, Resource after) {
270 		try {
271 			RepositoryResult<Statement> stmts;
272 			boolean autoCommit = conn.isAutoCommit();
273 			conn.setAutoCommit(false);
274 			stmts = conn.getStatements(before, null, null, false);
275 			try {
276 				while (stmts.hasNext()) {
277 					Statement stmt = stmts.next();
278 					URI pred = stmt.getPredicate();
279 					Value obj = stmt.getObject();
280 					conn.remove(before, pred, obj);
281 					conn.add(after, pred, obj);
282 				}
283 			} finally {
284 				stmts.close();
285 			}
286 			stmts = conn.getStatements(null, null, before);
287 			try {
288 				while (stmts.hasNext()) {
289 					Statement stmt = stmts.next();
290 					Resource subj = stmt.getSubject();
291 					URI pred = stmt.getPredicate();
292 					conn.remove(subj, pred, before);
293 					conn.add(subj, pred, after);
294 				}
295 			} finally {
296 				stmts.close();
297 			}
298 			conn.setAutoCommit(autoCommit);
299 		} catch (Exception e) {
300 			throw new ElmoPersistException(e);
301 		}
302 	}
303 
304 	private String getPrefix(String namespace) {
305 		CloseableIteration<? extends Namespace, RepositoryException> namespaces = null;
306 		try {
307 			try {
308 				namespaces = conn.getNamespaces();
309 				while (namespaces.hasNext()) {
310 					Namespace ns = namespaces.next();
311 					if (namespace.equals(ns.getName()))
312 						return ns.getPrefix();
313 				}
314 				return DEFAULT_PREFIX;
315 			} finally {
316 				if (namespaces != null)
317 					namespaces.close();
318 			}
319 		} catch (RepositoryException e) {
320 			throw new ElmoIOException(e);
321 		}
322 	}
323 
324 	private boolean isDisjointWith(Class<?> role, Set<Class<?>> roles) {
325 		disjointWith dist = role.getAnnotation(disjointWith.class);
326 		if (dist == null)
327 			return true;
328 		for (Class<?> c : dist.value()) {
329 			for (Class<?> r : roles) {
330 				if (c.isAssignableFrom(r))
331 					throw new AssertionError(role.getSimpleName()
332 							+ " cannot be assigned to a " + r.getSimpleName());
333 			}
334 		}
335 		return true;
336 	}
337 
338 }