2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2010 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.common.storage.jpa;
26 import org.collectionspace.services.common.context.ServiceContextProperties;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.List;
32 import javax.persistence.NoResultException;
33 import javax.persistence.PersistenceException;
34 import javax.persistence.Query;
36 import org.collectionspace.services.authorization.perms.Permission;
37 import org.collectionspace.services.authorization.PermissionValue;
38 import org.collectionspace.services.authorization.Role;
39 import org.collectionspace.services.authorization.RoleValue;
40 import org.collectionspace.services.authorization.AccountRoleRel;
41 import org.collectionspace.services.authorization.PermissionRoleRel;
42 import org.collectionspace.services.common.api.Tools;
43 import org.collectionspace.services.common.context.ServiceContext;
44 import org.collectionspace.services.common.document.BadRequestException;
45 import org.collectionspace.services.common.document.DocumentException;
46 import org.collectionspace.services.common.document.DocumentFilter;
47 import org.collectionspace.services.common.document.DocumentHandler;
48 import org.collectionspace.services.common.document.DocumentHandler.Action;
49 import org.collectionspace.services.common.document.DocumentNotFoundException;
50 import org.collectionspace.services.common.document.DocumentWrapper;
51 import org.collectionspace.services.common.document.DocumentWrapperImpl;
52 import org.collectionspace.services.common.document.JaxbUtils;
53 import org.hsqldb.lib.StringUtil;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * JpaRelationshipStorageClient deals with a relationship
59 * in persistent storage. This storage client deals with bulk operations, i.e.
60 * create/post inserts multiple tuples between the given object and subjects
61 * get retrieves all subjects for the given object in relationship
62 * delete deletes all subjects for the given object in relationship
65 @SuppressWarnings({ "rawtypes", "unchecked" })
66 public class JpaRelationshipStorageClient<T> extends JpaStorageClientImpl {
68 private final Logger logger = LoggerFactory.getLogger(JpaRelationshipStorageClient.class);
70 public JpaRelationshipStorageClient() {
75 * create of a relationship creates one or more relationships between
77 * the object and subjects of the relationship is chosen (by doc handler) from
82 * @throws BadRequestException
83 * @throws DocumentException
86 public String create(ServiceContext ctx,
87 DocumentHandler handler) throws BadRequestException,
91 JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection();
93 jpaTransactionContext.beginTransaction();
94 handler.prepare(Action.CREATE);
95 List<T> relationshipList = new ArrayList<T>();
96 DocumentWrapper<List<T>> wrapDoc = new DocumentWrapperImpl<List<T>>(relationshipList);
97 handler.handle(Action.CREATE, wrapDoc);
98 for (T relationship : relationshipList) {
99 JaxbUtils.setValue(relationship, "setCreatedAtItem", Date.class, new Date());
100 jpaTransactionContext.persist(relationship);
102 handler.complete(Action.CREATE, wrapDoc);
103 jpaTransactionContext.commitTransaction();
104 result = "0"; // meaningless result
105 } catch (BadRequestException bre) {
107 } catch (PersistenceException pe) {
109 } catch (DocumentException de) {
111 } catch (Exception e) {
112 throw new DocumentException(e);
114 if (result == null) {
115 jpaTransactionContext.markForRollback(); // If result == null, we failed and must mark the current tx for rollback
117 ctx.closeConnection();
124 * get retrieves all relationships for the object in the relationship
125 * identified by the id. the object could be a permission or a role
127 * @param id of the object in the relationship
129 * @throws DocumentNotFoundException
130 * @throws DocumentException
133 public void get(ServiceContext ctx, String id, DocumentHandler handler)
134 throws DocumentNotFoundException, DocumentException {
136 JPATransactionContext jpaConnectionContext = (JPATransactionContext)ctx.openConnection();
138 if (getObject(ctx, id) == null) {
139 String msg = "get: " + "could not find the object with id=" + id;
141 throw new DocumentNotFoundException(msg);
144 String objectId = getObjectId(ctx);
145 DocumentFilter docFilter = handler.getDocumentFilter();
146 if (docFilter == null) {
147 docFilter = handler.createDocumentFilter();
150 handler.prepare(Action.GET);
151 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
152 queryStrBldr.append(getEntityName(ctx));
153 queryStrBldr.append(" a");
155 String joinFetch = docFilter.getJoinFetchClause();
156 if (Tools.notBlank(joinFetch)) {
157 queryStrBldr.append(" " + joinFetch);
160 queryStrBldr.append(" WHERE " + objectId + " = :objectId");
161 String where = docFilter.getWhereClause();
162 if ((null != where) && (where.length() > 0)) {
163 queryStrBldr.append(" AND " + where);
166 String queryStr = queryStrBldr.toString(); //for debugging
167 if (logger.isDebugEnabled()) {
168 logger.debug("get: jql=" + queryStr.toString());
170 Query q = jpaConnectionContext.createQuery(queryStr);
171 q.setParameter("objectId", id);
173 List<T> relList = new ArrayList<T>();
174 jpaConnectionContext.beginTransaction();
176 relList = q.getResultList();
177 } catch (NoResultException nre) {
178 // Quietly consume. relList will just be an empty list
181 DocumentWrapper<List<T>> wrapDoc = new DocumentWrapperImpl<List<T>>(relList);
182 handler.handle(Action.GET, wrapDoc);
183 handler.complete(Action.GET, wrapDoc);
184 jpaConnectionContext.commitTransaction();
185 } catch (DocumentNotFoundException nfe) {
186 jpaConnectionContext.markForRollback();
188 } catch (DocumentException de) {
189 jpaConnectionContext.markForRollback();
191 } catch (Exception e) {
192 jpaConnectionContext.markForRollback();
193 if (logger.isDebugEnabled()) {
194 logger.debug("Caught exception ", e);
196 throw new DocumentException(e);
198 ctx.closeConnection();
205 * @param relationship the relationship
208 private Long getId(T relationship) {
211 if (relationship != null) {
212 if (relationship instanceof AccountRoleRel) {
213 AccountRoleRel accountRoleRel = (AccountRoleRel)relationship;
214 result = accountRoleRel.getHjid();
215 } else if (relationship instanceof PermissionRoleRel) {
216 PermissionRoleRel permissionRoleRel = (PermissionRoleRel)relationship;
217 result = permissionRoleRel.getHjid();
225 * Gets the relationship.
228 * @param relationship the relationship
229 * @return the relationship
230 * @throws DocumentNotFoundException the document not found exception
232 private T getRelationship(JPATransactionContext jpaTransactionContext, T relationship)
233 throws DocumentNotFoundException {
234 Long id = getId(relationship);
236 T relationshipFound = (T)jpaTransactionContext.find(relationship.getClass(), id);
237 if (relationshipFound == null) {
238 String msg = "Could not find relationship with id=" + id;
239 if (logger.isErrorEnabled() == true) {
242 throw new DocumentNotFoundException(msg);
245 return relationshipFound;
249 * delete removes all the relationships for the object in the relationship
250 * identified by the id. the object could be a permission or a role
252 * @param id of the object in the relationship
253 * @throws DocumentNotFoundException
254 * @throws DocumentException
257 public void delete(ServiceContext ctx, String id)
258 throws DocumentNotFoundException,
261 if (getObject(ctx, id) == null) {
262 String msg = "delete : " + "could not find the object with id=" + id;
264 throw new DocumentNotFoundException(msg);
267 String objectId = getObjectId(ctx);
268 if (logger.isDebugEnabled()) {
269 logger.debug("delete: using objectId=" + objectId);
271 Class objectClass = getObjectClass(ctx);
272 if (logger.isDebugEnabled()) {
273 logger.debug("delete: using object class=" + objectClass.getName());
276 JPATransactionContext jpaConnectionContext = (JPATransactionContext)ctx.openConnection();
278 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
279 String entityName = getEntityName(ctx);
280 deleteStr.append(entityName);
281 deleteStr.append(" WHERE " + objectId + " = :objectId");
282 if (logger.isDebugEnabled()) {
283 logger.debug("delete: jql=" + deleteStr.toString());
285 Query q = jpaConnectionContext.createQuery(deleteStr.toString());
286 q.setParameter("objectId", id);
288 jpaConnectionContext.beginTransaction();
289 if (logger.isDebugEnabled() == true) {
290 logger.debug(q.toString());
292 rcount = q.executeUpdate();
293 if (logger.isDebugEnabled()) {
294 logger.debug("deleted " + rcount + " relationships for entity " + entityName
295 + " with objectId=" + objectId);
297 jpaConnectionContext.commitTransaction();
298 } catch (Exception e) {
299 jpaConnectionContext.markForRollback();
300 if (logger.isDebugEnabled()) {
301 logger.debug("Caught exception ", e);
303 throw new DocumentException(e);
305 ctx.closeConnection();
310 * delete of a relationship deletes one or more relationships between
311 * permission and role
312 * the object and subjects of the relationship is chosen (by doc handler) from
317 * @throws DocumentNotFoundException
318 * @throws DocumentException
321 public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
322 throws DocumentNotFoundException, DocumentException {
323 boolean result = true;
325 JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection();
327 jpaTransactionContext.beginTransaction();
328 handler.prepare(Action.DELETE);
329 List<T> relationshipList = new ArrayList<T>();
330 DocumentWrapper<List<T>> wrapDoc = new DocumentWrapperImpl<List<T>>(relationshipList);
331 handler.handle(Action.DELETE, wrapDoc);
333 //the following could be much more efficient if done with a single sql/jql
335 for (T relationship : relationshipList) {
336 jpaTransactionContext.remove(getRelationship(jpaTransactionContext, relationship));
338 handler.complete(Action.DELETE, wrapDoc); // Delete from the Spring Security tables. Would be better if this was part of the earlier transaction.
339 jpaTransactionContext.commitTransaction();
340 } catch (DocumentException de) {
341 jpaTransactionContext.markForRollback();
343 } catch (Exception e) {
344 jpaTransactionContext.markForRollback();
345 if (logger.isDebugEnabled()) {
346 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
348 throw new DocumentException(e);
350 ctx.closeConnection();
357 * getObjectId returns the id of the object in a relationship
361 protected String getObjectId(ServiceContext ctx) {
362 String objectId = (String) ctx.getProperty(ServiceContextProperties.OBJECT_ID);
364 if (objectId == null) {
365 String msg = ServiceContextProperties.OBJECT_ID + " property is missing in the context";
367 throw new IllegalArgumentException(msg);
374 * getObjectClass returns the class of the object in a relationship
378 protected Class getObjectClass(ServiceContext ctx) {
379 Class objectClass = (Class) ctx.getProperty(ServiceContextProperties.OBJECT_CLASS);
381 if (objectClass == null) {
382 String msg = ServiceContextProperties.OBJECT_CLASS + " property is missing in the context";
384 throw new IllegalArgumentException(msg);
391 * getObject returns the object in the relationship
396 protected Object getObject(ServiceContext ctx, String id)
397 throws DocumentNotFoundException {
398 Class objectClass = getObjectClass(ctx);
399 return JpaStorageUtils.getEntity((JPATransactionContext)ctx.getCurrentTransactionContext(), id, objectClass);