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.api.Tools;
53 import org.collectionspace.services.common.authorization_mgt.ActionGroup;
54 import org.collectionspace.services.common.context.ServiceContext;
55 import org.collectionspace.services.common.document.BadRequestException;
56 import org.collectionspace.services.common.document.DocumentException;
57 import org.collectionspace.services.common.invocable.Invocable;
58 import org.collectionspace.services.common.invocable.InvocationContext;
59 import org.collectionspace.services.common.invocable.InvocationResults;
60 import org.collectionspace.services.common.invocable.Invocable.InvocationError;
62 import org.jboss.resteasy.spi.ResteasyProviderFactory;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
66 public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler<BatchCommon> {
67 private final Logger logger = LoggerFactory.getLogger(BatchDocumentModelHandler.class);
69 protected final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode();
72 * Return true if the batch job supports the requested mode.
73 * @param invocationCtx
76 * @throws BadRequestException
78 protected boolean supportsInvokationMode(InvocationContext invocationCtx, BatchCommon batchCommon) throws BadRequestException {
79 boolean result = false;
81 String invocationMode = invocationCtx.getMode().toLowerCase();
82 if (BatchInvocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
83 result = batchCommon.isSupportsSingleDoc(); //BatchJAXBSchema.SUPPORTS_SINGLE_DOC;
84 } else if (BatchInvocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) {
85 result = batchCommon.isSupportsDocList(); //BatchJAXBSchema.SUPPORTS_DOC_LIST;
86 } else if (BatchInvocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) {
87 result = batchCommon.isSupportsGroup(); //BatchJAXBSchema.SUPPORTS_GROUP;
88 } else if (Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationMode)) {
89 result = batchCommon.isSupportsNoContext(); //InvocableJAXBSchema.SUPPORTS_NO_CONTEXT;
91 String msg = String.format("BatchResource: Unknown invocation mode '%s' requested trying to invoke batch job '%s'.",
92 invocationMode, batchCommon.getName());
93 throw new BadRequestException(msg);
100 * Returns true if we found any required permissions.
105 private boolean hasRequiredPermissions(BatchCommon batchCommon) {
106 boolean result = false;
109 result = batchCommon.getResourceActionGroupList().getResourceActionGroup().size() > 0;
110 } catch (NullPointerException e) {
111 // ignore exception, we're just testing to see if we have any list elements
118 * Returns true if we found any required roles.
123 private boolean hasRequiredRoles(BatchCommon batchCommon) {
124 boolean result = false;
127 result = batchCommon.getForRoles().getRoleDisplayName().size() > 0;
128 } catch (NullPointerException e) {
129 // ignore exception, we're just testing to see if we have any list elements
136 * The current user is authorized to run the batch job if:
137 * 1. No permissions or roles are specified in the batch job
138 * 2. No roles are specified, but permissions are specified and the current user has those permissions
139 * 3. Roles are specified and the current user is a member of at least one of the roles.
144 protected boolean isAuthoritzed(BatchCommon batchCommon) {
145 boolean result = true;
147 if (hasRequiredRoles(batchCommon)) {
148 result = isAuthorizedWithRoles(batchCommon);
149 } else if (hasRequiredPermissions(batchCommon)) {
150 result = isAuthoritzedWithPermissions(batchCommon);
156 protected boolean isAuthorizedWithRoles(BatchCommon batchCommon) {
157 boolean result = false;
159 ForRoles forRolesList = batchCommon.getForRoles();
160 if (forRolesList != null) {
161 AccountResource accountResource = new AccountResource();
162 List<String> roleDisplayNameList = accountResource.getAccountRoleDisplayNames(AuthN.get().getUserId(), AuthN.get().getCurrentTenantId());
163 for (String target : forRolesList.getRoleDisplayName()) {
164 if (Tools.listContainsIgnoreCase(roleDisplayNameList, target)) {
175 * Check to see if the current user is authorized to run/invoke this batch job. If the batch job
176 * did not specify any permissions, we assume that the current user is authorized to run the job.
180 protected boolean isAuthoritzedWithPermissions(BatchCommon batchCommon) {
181 boolean result = true;
183 ResourceActionGroupList resourceActionGroupList = batchCommon.getResourceActionGroupList();
184 if (resourceActionGroupList != null) {
185 String tenantId = AuthN.get().getCurrentTenantId();
186 for (ResourceActionGroup resourceActionGroup: resourceActionGroupList.getResourceActionGroup()) {
187 String resourceName = resourceActionGroup.getResourceName();
188 ActionGroup actionGroup = ActionGroup.creatActionGroup(resourceActionGroup.getActionGroup());
189 for (ActionType actionType: actionGroup.getActions()) {
190 CSpaceResource res = new URIResourceImpl(tenantId, resourceName, AuthZ.getMethod(actionType));
191 if (AuthZ.get().isAccessAllowed(res) == false) {
202 * Returns a copy of the incoming list of strings all lower-cased. Also removes any duplicates.
204 * @param listOfStrings
207 private List<String> toLowerCase(List<String> listOfStrings) {
208 List<String> result = null;
210 if (listOfStrings != null) {
211 Set<String> stringSet = new HashSet<String>();
212 for (String s : listOfStrings) {
213 stringSet.add(s.toLowerCase());
215 result = new ArrayList<String>(stringSet);
221 public InvocationResults invokeBatchJob(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String csid,
222 ResourceMap resourceMap, InvocationContext invocationCtx, BatchCommon batchCommon) throws Exception {
223 CoreSessionInterface repoSession = null;
224 boolean releaseRepoSession = false;
226 NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl) this.getRepositoryClient(ctx);
227 if (repoSession == null) {
228 repoSession = repoClient.getRepositorySession(ctx);
229 releaseRepoSession = true;
232 // Get properties from the batch docModel, and release the session
235 // Ensure the current user has permission to run this batch job
236 if (isAuthoritzed(batchCommon) == false) {
237 String msg = String.format("BatchResource: The user '%s' does not have permission to run the batch job '%s' CSID='%s'",
238 AuthN.get().getUserId(), batchCommon.getName(), csid);
239 throw new PermissionException(msg);
243 // Ensure the batch job supports the requested invocation context's mode type
244 if (supportsInvokationMode(invocationCtx, batchCommon) == false) {
245 String msg = String.format("BatchResource: The batch job '%s' CSID='%s' does not support the invocation mode '%s'.",
246 batchCommon.getName(), csid, invocationCtx.getMode());
247 throw new BadRequestException(msg);
251 // Ensure the batch job supports the requested invocation context's document type
252 if (!Invocable.INVOCATION_MODE_NO_CONTEXT.equalsIgnoreCase(invocationCtx.getMode())) {
253 ForDocTypes forDocTypes = batchCommon.getForDocTypes();
254 if (forDocTypes != null) {
255 List<String> forDocTypeList = toLowerCase(forDocTypes.getForDocType()); // convert all strings to lowercase.
256 if (forDocTypeList == null || !forDocTypeList.contains(invocationCtx.getDocType().toLowerCase())) {
257 String msg = String.format("BatchResource: The batch job '%s' CSID='%s' does not support the invocation document type '%s'.",
258 batchCommon.getName(), csid, invocationCtx.getDocType());
259 throw new BadRequestException(msg);
265 // Now that we've ensure all the prerequisites have been met, let's try to
266 // instantiate and run the batch job.
269 String className = batchCommon.getClassName().trim();
270 ClassLoader tccl = Thread.currentThread().getContextClassLoader();
271 Class<?> c = tccl.loadClass(className);
272 tccl.setClassAssertionStatus(className, true);
273 if (!BatchInvocable.class.isAssignableFrom(c)) {
274 throw new RuntimeException("BatchResource: Class: " + className + " does not implement BatchInvocable!");
277 BatchInvocable batchInstance = (BatchInvocable) c.newInstance();
278 List<String> modes = batchInstance.getSupportedInvocationModes();
279 if (!modes.contains(invocationCtx.getMode().toLowerCase())) {
280 String msg = String.format("BatchResource: Invoked with unsupported mode '%s'. Batch class '%s' supports these modes: %s.",
281 invocationCtx.getMode().toLowerCase(), className, modes.toString());
282 throw new BadRequestException(msg);
285 batchInstance.setInvocationContext(invocationCtx);
286 batchInstance.setServiceContext(ctx);
288 if (resourceMap != null) {
289 batchInstance.setResourceMap(resourceMap);
291 resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class);
292 if (resourceMap != null) {
293 batchInstance.setResourceMap(resourceMap);
295 logger.warn("BatchResource.invoke did not get a resourceMapHolder in context!");
300 batchInstance.run(batchCommon);
301 } catch (UnsupportedOperationException t) {
302 // Support for run() will be deprecated in a future release. See DRYD-878
303 logger.debug(t.getMessage());
307 int status = batchInstance.getCompletionStatus();
308 if (status == Invocable.STATUS_ERROR) {
309 InvocationError error = batchInstance.getErrorInfo();
310 if (error.getResponseCode() == BAD_REQUEST_STATUS) {
311 throw new BadRequestException("BatchResouce: batchProcess encountered error: "
312 + batchInstance.getErrorInfo());
314 throw new RuntimeException("BatchResouce: batchProcess encountered error: "
315 + batchInstance.getErrorInfo());
320 InvocationResults results = batchInstance.getResults();
322 } catch (PermissionException e) {
323 if (logger.isDebugEnabled()) {
324 logger.debug("BatchResource: Caught exception ", e);
327 } catch (Exception e) {
328 if (logger.isDebugEnabled()) {
329 logger.debug("BatchResource: Caught exception ", e);
331 throw new DocumentException(e);
333 if (releaseRepoSession && repoSession != null) {
334 repoClient.releaseRepositorySession(ctx, repoSession);