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 2009 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.batch.nuxeo;
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.List;
31 import javax.ws.rs.core.Response;
33 import org.collectionspace.authentication.AuthN;
34 import org.collectionspace.services.account.AccountResource;
35 import org.collectionspace.services.authorization.AuthZ;
36 import org.collectionspace.services.authorization.CSpaceResource;
37 import org.collectionspace.services.authorization.PermissionException;
38 import org.collectionspace.services.authorization.URIResourceImpl;
39 import org.collectionspace.services.authorization.perms.ActionType;
40 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
41 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
42 import org.collectionspace.services.nuxeo.client.java.NuxeoRepositoryClientImpl;
43 import org.collectionspace.services.batch.BatchCommon;
44 import org.collectionspace.services.batch.BatchCommon.ForDocTypes;
45 import org.collectionspace.services.batch.BatchCommon.ForRoles;
46 import org.collectionspace.services.batch.BatchInvocable;
47 import org.collectionspace.services.batch.ResourceActionGroup;
48 import org.collectionspace.services.batch.ResourceActionGroupList;
49 import org.collectionspace.services.client.PoxPayloadIn;
50 import org.collectionspace.services.client.PoxPayloadOut;
51 import org.collectionspace.services.common.ResourceMap;
52 import org.collectionspace.services.common.authorization_mgt.ActionGroup;
53 import org.collectionspace.services.common.context.ServiceContext;
54 import org.collectionspace.services.common.document.BadRequestException;
55 import org.collectionspace.services.common.document.DocumentException;
56 import org.collectionspace.services.common.invocable.Invocable;
57 import org.collectionspace.services.common.invocable.InvocationContext;
58 import org.collectionspace.services.common.invocable.InvocationResults;
59 import org.collectionspace.services.common.invocable.Invocable.InvocationError;
61 import org.jboss.resteasy.spi.ResteasyProviderFactory;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
65 public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler<BatchCommon> {
66 private final Logger logger = LoggerFactory.getLogger(BatchDocumentModelHandler.class);
68 protected final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode();
71 * Return true if the batch job supports the requested mode.
72 * @param invocationCtx
75 * @throws BadRequestException
77 protected boolean supportsInvokationMode(InvocationContext invocationCtx, BatchCommon batchCommon) throws BadRequestException {
78 boolean result = false;
80 String invocationMode = invocationCtx.getMode().toLowerCase();
81 if (BatchInvocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
82 result = batchCommon.isSupportsSingleDoc(); //BatchJAXBSchema.SUPPORTS_SINGLE_DOC;
83 } else if (BatchInvocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) {
84 result = batchCommon.isSupportsDocList(); //BatchJAXBSchema.SUPPORTS_DOC_LIST;
85 } else if (BatchInvocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) {
86 result = batchCommon.isSupportsGroup(); //BatchJAXBSchema.SUPPORTS_GROUP;
87 } else if (Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationMode)) {
88 result = batchCommon.isSupportsNoContext(); //InvocableJAXBSchema.SUPPORTS_NO_CONTEXT;
90 String msg = String.format("BatchResource: Unknown invocation mode '%s' requested trying to invoke batch job '%s'.",
91 invocationMode, batchCommon.getName());
92 throw new BadRequestException(msg);
99 * Returns true if we found any required permissions.
104 private boolean hasRequiredPermissions(BatchCommon batchCommon) {
105 boolean result = false;
108 result = batchCommon.getResourceActionGroupList().getResourceActionGroup().size() > 0;
109 } catch (NullPointerException e) {
110 // ignore exception, we're just testing to see if we have any list elements
117 * Returns true if we found any required roles.
122 private boolean hasRequiredRoles(BatchCommon batchCommon) {
123 boolean result = false;
126 result = batchCommon.getForRoles().getRoleDisplayName().size() > 0;
127 } catch (NullPointerException e) {
128 // ignore exception, we're just testing to see if we have any list elements
135 * The current user is authorized to run the batch job if:
136 * 1. No permissions or roles are specified in the batch job
137 * 2. No roles are specified, but permissions are specified and the current user has those permissions
138 * 3. Roles are specified and the current user is a member of at least one of the roles.
143 protected boolean isAuthoritzed(BatchCommon batchCommon) {
144 boolean result = true;
146 if (hasRequiredRoles(batchCommon)) {
147 result = isAuthorizedWithRoles(batchCommon);
148 } else if (hasRequiredPermissions(batchCommon)) {
149 result = isAuthoritzedWithPermissions(batchCommon);
155 protected boolean isAuthorizedWithRoles(BatchCommon batchCommon) {
156 boolean result = false;
158 ForRoles forRolesList = batchCommon.getForRoles();
159 if (forRolesList != null) {
160 AccountResource accountResource = new AccountResource();
161 List<String> roleDisplayNameList = accountResource.getAccountRoles(AuthN.get().getUserId(), AuthN.get().getCurrentTenantId());
162 for (String target : forRolesList.getRoleDisplayName()) {
163 if (roleDisplayNameList.contains(target)) {
174 * Check to see if the current user is authorized to run/invoke this batch job. If the batch job
175 * did not specify any permissions, we assume that the current user is authorized to run the job.
179 protected boolean isAuthoritzedWithPermissions(BatchCommon batchCommon) {
180 boolean result = true;
182 ResourceActionGroupList resourceActionGroupList = batchCommon.getResourceActionGroupList();
183 if (resourceActionGroupList != null) {
184 String tenantId = AuthN.get().getCurrentTenantId();
185 for (ResourceActionGroup resourceActionGroup: resourceActionGroupList.getResourceActionGroup()) {
186 String resourceName = resourceActionGroup.getResourceName();
187 ActionGroup actionGroup = ActionGroup.creatActionGroup(resourceActionGroup.getActionGroup());
188 for (ActionType actionType: actionGroup.getActions()) {
189 CSpaceResource res = new URIResourceImpl(tenantId, resourceName, AuthZ.getMethod(actionType));
190 if (AuthZ.get().isAccessAllowed(res) == false) {
201 * Returns a copy of the incoming list of strings all lower-cased. Also removes any duplicates.
203 * @param listOfStrings
206 private List<String> toLowerCase(List<String> listOfStrings) {
207 List<String> result = null;
209 if (listOfStrings != null) {
210 Set<String> stringSet = new HashSet<String>();
211 for (String s : listOfStrings) {
212 stringSet.add(s.toLowerCase());
214 result = new ArrayList<String>(stringSet);
220 public InvocationResults invokeBatchJob(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String csid,
221 ResourceMap resourceMap, InvocationContext invocationCtx, BatchCommon batchCommon) throws Exception {
222 CoreSessionInterface repoSession = null;
223 boolean releaseRepoSession = false;
225 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl) this.getRepositoryClient(ctx);
226 if (repoSession == null) {
227 repoSession = repoClient.getRepositorySession(ctx);
228 releaseRepoSession = true;
231 // Get properties from the batch docModel, and release the session
234 // Ensure the current user has permission to run this batch job
235 if (isAuthoritzed(batchCommon) == false) {
236 String msg = String.format("BatchResource: The user '%s' does not have permission to run the batch job '%s' CSID='%s'",
237 AuthN.get().getUserId(), batchCommon.getName(), csid);
238 throw new PermissionException(msg);
242 // Ensure the batch job supports the requested invocation context's mode type
243 if (supportsInvokationMode(invocationCtx, batchCommon) == false) {
244 String msg = String.format("BatchResource: The batch job '%s' CSID='%s' does not support the invocation mode '%s'.",
245 batchCommon.getName(), csid, invocationCtx.getMode());
246 throw new BadRequestException(msg);
250 // Ensure the batch job supports the requested invocation context's document type
251 if (!Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationCtx.getMode())) {
252 ForDocTypes forDocTypes = batchCommon.getForDocTypes();
253 if (forDocTypes != null) {
254 List<String> forDocTypeList = toLowerCase(forDocTypes.getForDocType()); // convert all strings to lowercase.
255 if (forDocTypeList == null || !forDocTypeList.contains(invocationCtx.getDocType().toLowerCase())) {
256 String msg = String.format("BatchResource: The batch job '%s' CSID='%s' does not support the invocation document type '%s'.",
257 batchCommon.getName(), csid, invocationCtx.getDocType());
258 throw new BadRequestException(msg);
264 // Now that we've ensure all the prerequisites have been met, let's try to
265 // instantiate and run the batch job.
268 String className = batchCommon.getClassName().trim();
269 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
270 Class<?> c = tccl.loadClass(className);
271 tccl.setClassAssertionStatus(className, true);
272 if (!BatchInvocable.class.isAssignableFrom(c)) {
273 throw new RuntimeException("BatchResource: Class: " + className + " does not implement BatchInvocable!");
276 BatchInvocable batchInstance = (BatchInvocable) c.newInstance();
277 List<String> modes = batchInstance.getSupportedInvocationModes();
278 if (!modes.contains(invocationCtx.getMode().toLowerCase())) {
279 String msg = String.format("BatchResource: Invoked with unsupported mode '%s'. Batch class '%s' supports these modes: %s.",
280 invocationCtx.getMode().toLowerCase(), className, modes.toString());
281 throw new BadRequestException(msg);
284 batchInstance.setInvocationContext(invocationCtx);
285 batchInstance.setServiceContext(ctx);
287 if (resourceMap != null) {
288 batchInstance.setResourceMap(resourceMap);
290 resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class);
291 if (resourceMap != null) {
292 batchInstance.setResourceMap(resourceMap);
294 logger.warn("BatchResource.invoke did not get a resourceMapHolder in context!");
299 int status = batchInstance.getCompletionStatus();
300 if (status == Invocable.STATUS_ERROR) {
301 InvocationError error = batchInstance.getErrorInfo();
302 if (error.getResponseCode() == BAD_REQUEST_STATUS) {
303 throw new BadRequestException("BatchResouce: batchProcess encountered error: "
304 + batchInstance.getErrorInfo());
306 throw new RuntimeException("BatchResouce: batchProcess encountered error: "
307 + batchInstance.getErrorInfo());
312 InvocationResults results = batchInstance.getResults();
314 } catch (PermissionException e) {
315 if (logger.isDebugEnabled()) {
316 logger.debug("BatchResource: Caught exception ", e);
319 } catch (Exception e) {
320 if (logger.isDebugEnabled()) {
321 logger.debug("BatchResource: Caught exception ", e);
323 throw new DocumentException(e);
325 if (releaseRepoSession && repoSession != null) {
326 repoClient.releaseRepositorySession(ctx, repoSession);