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 static PermissionValue createPermissionValue(Permission permission) {
71 PermissionValue result = new PermissionValue();
72 result.setPermissionId(permission.getCsid());
73 result.setResourceName(permission.getResourceName());
74 result.setActionGroup(permission.getActionGroup());
78 public static RoleValue createRoleValue(Role role) {
79 RoleValue result = new RoleValue();
80 result.setRoleId(role.getCsid());
81 result.setRoleName(role.getRoleName());
85 public JpaRelationshipStorageClient() {
90 * create of a relationship creates one or more relationships between
92 * the object and subjects of the relationship is chosen (by doc handler) from
97 * @throws BadRequestException
98 * @throws DocumentException
101 public String create(ServiceContext ctx,
102 DocumentHandler handler) throws BadRequestException,
104 String result = null;
106 JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection();
108 jpaTransactionContext.beginTransaction();
109 handler.prepare(Action.CREATE);
110 List<T> rl = new ArrayList<T>();
111 DocumentWrapper<List<T>> wrapDoc =
112 new DocumentWrapperImpl<List<T>>(rl);
113 handler.handle(Action.CREATE, wrapDoc);
115 JaxbUtils.setValue(r, "setCreatedAtItem", Date.class, new Date());
116 jpaTransactionContext.persist(r);
118 handler.complete(Action.CREATE, wrapDoc);
119 jpaTransactionContext.commitTransaction();
120 result = "-1"; // meaningless result
121 } catch (BadRequestException bre) {
122 jpaTransactionContext.markForRollback();
124 } catch (PersistenceException pe) {
125 jpaTransactionContext.markForRollback();
127 } catch (Exception e) {
128 jpaTransactionContext.markForRollback();
129 if (logger.isDebugEnabled()) {
130 logger.debug("Caught exception ", e);
132 throw new DocumentException(e);
134 ctx.closeConnection();
141 * get retrieves all relationships for the object in the relationship
142 * identified by the id. the object could be a permission or a role
144 * @param id of the object in the relationship
146 * @throws DocumentNotFoundException
147 * @throws DocumentException
150 public void get(ServiceContext ctx, String id, DocumentHandler handler)
151 throws DocumentNotFoundException, DocumentException {
153 JPATransactionContext jpaConnectionContext = (JPATransactionContext)ctx.openConnection();
155 if (getObject(ctx, id) == null) {
156 String msg = "get: " + "could not find the object with id=" + id;
158 throw new DocumentNotFoundException(msg);
161 String objectId = getObjectId(ctx);
162 Class objectClass = getObjectClass(ctx);
163 DocumentFilter docFilter = handler.getDocumentFilter();
164 if (docFilter == null) {
165 docFilter = handler.createDocumentFilter();
168 handler.prepare(Action.GET);
169 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
170 queryStrBldr.append(getEntityName(ctx));
171 queryStrBldr.append(" a");
173 String joinFetch = docFilter.getJoinFetchClause();
174 if (Tools.notBlank(joinFetch)) {
175 queryStrBldr.append(" " + joinFetch);
178 queryStrBldr.append(" WHERE " + objectId + " = :objectId");
179 String where = docFilter.getWhereClause();
180 if ((null != where) && (where.length() > 0)) {
181 queryStrBldr.append(" AND " + where);
184 String queryStr = queryStrBldr.toString(); //for debugging
185 if (logger.isDebugEnabled()) {
186 logger.debug("get: jql=" + queryStr.toString());
188 Query q = jpaConnectionContext.createQuery(queryStr);
189 q.setParameter("objectId", id);
191 List<T> relList = new ArrayList<T>();
192 jpaConnectionContext.beginTransaction();
194 relList = q.getResultList();
195 } catch (NoResultException nre) {
196 String msg = "get(1): " + " could not find relationships for object class="
197 + objectClass.getName() + " id=" + id;
198 if (logger.isDebugEnabled()) {
199 logger.debug(msg, nre);
202 if (relList.size() == 0) {
203 String msg = "get(2): " + " could not find relationships for object class="
204 + objectClass.getName() + " id=" + id;
205 if (logger.isDebugEnabled()) {
209 DocumentWrapper<List<T>> wrapDoc = new DocumentWrapperImpl<List<T>>(relList);
210 handler.handle(Action.GET, wrapDoc);
211 handler.complete(Action.GET, wrapDoc);
212 jpaConnectionContext.commitTransaction();
213 } catch (DocumentNotFoundException nfe) {
214 jpaConnectionContext.markForRollback();
216 } catch (DocumentException de) {
217 jpaConnectionContext.markForRollback();
219 } catch (Exception e) {
220 jpaConnectionContext.markForRollback();
221 if (logger.isDebugEnabled()) {
222 logger.debug("Caught exception ", e);
224 throw new DocumentException(e);
226 ctx.closeConnection();
233 * @param relationship the relationship
236 private Long getId(T relationship) {
239 if (relationship != null) {
240 if (relationship instanceof AccountRoleRel) {
241 AccountRoleRel accountRoleRel = (AccountRoleRel)relationship;
242 result = accountRoleRel.getHjid();
243 } else if (relationship instanceof PermissionRoleRel) {
244 PermissionRoleRel permissionRoleRel = (PermissionRoleRel)relationship;
245 result = permissionRoleRel.getHjid();
253 * Gets the relationship.
256 * @param relationship the relationship
257 * @return the relationship
258 * @throws DocumentNotFoundException the document not found exception
260 private T getRelationship(JPATransactionContext jpaTransactionContext, T relationship)
261 throws DocumentNotFoundException {
262 Long id = getId(relationship);
264 T relationshipFound = (T)jpaTransactionContext.find(relationship.getClass(), id);
265 if (relationshipFound == null) {
266 String msg = "Could not find relationship with id=" + id;
267 if (logger.isErrorEnabled() == true) {
270 throw new DocumentNotFoundException(msg);
273 return relationshipFound;
277 * delete removes all the relationships for the object in the relationship
278 * identified by the id. the object could be a permission or a role
280 * @param id of the object in the relationship
281 * @throws DocumentNotFoundException
282 * @throws DocumentException
285 public void delete(ServiceContext ctx, String id)
286 throws DocumentNotFoundException,
289 if (getObject(ctx, id) == null) {
290 String msg = "delete : " + "could not find the object with id=" + id;
292 throw new DocumentNotFoundException(msg);
295 String objectId = getObjectId(ctx);
296 if (logger.isDebugEnabled()) {
297 logger.debug("delete: using objectId=" + objectId);
299 Class objectClass = getObjectClass(ctx);
300 if (logger.isDebugEnabled()) {
301 logger.debug("delete: using object class=" + objectClass.getName());
304 JPATransactionContext jpaConnectionContext = (JPATransactionContext)ctx.openConnection();
306 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
307 String entityName = getEntityName(ctx);
308 deleteStr.append(entityName);
309 deleteStr.append(" WHERE " + objectId + " = :objectId");
310 if (logger.isDebugEnabled()) {
311 logger.debug("delete: jql=" + deleteStr.toString());
313 Query q = jpaConnectionContext.createQuery(deleteStr.toString());
314 q.setParameter("objectId", id);
316 jpaConnectionContext.beginTransaction();
317 if (logger.isDebugEnabled() == true) {
318 logger.debug(q.toString());
320 rcount = q.executeUpdate();
321 if (logger.isDebugEnabled()) {
322 logger.debug("deleted " + rcount + " relationships for entity " + entityName
323 + " with objectId=" + objectId);
325 jpaConnectionContext.commitTransaction();
326 } catch (Exception e) {
327 jpaConnectionContext.markForRollback();
328 if (logger.isDebugEnabled()) {
329 logger.debug("Caught exception ", e);
331 throw new DocumentException(e);
333 ctx.closeConnection();
338 * delete of a relationship deletes one or more relationships between
339 * permission and role
340 * the object and subjects of the relationship is chosen (by doc handler) from
345 * @throws DocumentNotFoundException
346 * @throws DocumentException
349 public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
350 throws DocumentNotFoundException, DocumentException {
351 boolean result = true;
353 JPATransactionContext jpaTransactionContext = (JPATransactionContext)ctx.openConnection();
355 jpaTransactionContext.beginTransaction();
356 handler.prepare(Action.DELETE);
357 List<T> rl = new ArrayList<T>();
358 DocumentWrapper<List<T>> wrapDoc = new DocumentWrapperImpl<List<T>>(rl);
359 handler.handle(Action.DELETE, wrapDoc);
361 //the following could be much more efficient if done with a single sql/jql
364 jpaTransactionContext.remove(getRelationship(jpaTransactionContext, r));
366 handler.complete(Action.DELETE, wrapDoc); // Delete from the Spring Security tables. Would be better if this was part of the earlier transaction.
367 jpaTransactionContext.commitTransaction();
368 } catch (DocumentException de) {
369 jpaTransactionContext.markForRollback();
371 } catch (Exception e) {
372 jpaTransactionContext.markForRollback();
373 if (logger.isDebugEnabled()) {
374 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
376 throw new DocumentException(e);
378 ctx.closeConnection();
385 * getObjectId returns the id of the object in a relationship
389 protected String getObjectId(ServiceContext ctx) {
390 String objectId = (String) ctx.getProperty(ServiceContextProperties.OBJECT_ID);
392 if (objectId == null) {
393 String msg = ServiceContextProperties.OBJECT_ID + " property is missing in the context";
395 throw new IllegalArgumentException(msg);
402 * getObjectClass returns the class of the object in a relationship
406 protected Class getObjectClass(ServiceContext ctx) {
407 Class objectClass = (Class) ctx.getProperty(ServiceContextProperties.OBJECT_CLASS);
409 if (objectClass == null) {
410 String msg = ServiceContextProperties.OBJECT_CLASS + " property is missing in the context";
412 throw new IllegalArgumentException(msg);
419 * getObject returns the object in the relationship
424 protected Object getObject(ServiceContext ctx, String id)
425 throws DocumentNotFoundException {
426 Class objectClass = getObjectClass(ctx);
427 return JpaStorageUtils.getEntity((JPATransactionContext)ctx.getCurrentTransactionContext(), id, objectClass);