1 package org.collectionspace.services.jaxrs;
3 import static org.nuxeo.elasticsearch.ElasticSearchConstants.ES_ENABLED_PROPERTY;
5 import javax.servlet.ServletContextEvent;
6 import javax.ws.rs.core.PathSegment;
7 import javax.ws.rs.core.Response;
8 import javax.ws.rs.core.UriInfo;
10 import org.jboss.resteasy.core.Dispatcher;
11 import org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap;
12 import org.jboss.resteasy.specimpl.PathSegmentImpl;
13 import org.apache.commons.io.IOUtils;
14 import org.collectionspace.authentication.AuthN;
15 import org.collectionspace.authentication.CSpaceTenant;
16 import org.collectionspace.services.account.Tenant;
17 import org.collectionspace.services.account.TenantResource;
18 import org.collectionspace.services.authorization.AuthZ;
19 import org.collectionspace.services.batch.BatchResource;
20 import org.collectionspace.services.client.AbstractCommonListUtils;
21 import org.collectionspace.services.client.AuthorityClient;
22 import org.collectionspace.services.client.BatchClient;
23 import org.collectionspace.services.client.CollectionSpaceClient;
24 import org.collectionspace.services.client.PayloadOutputPart;
25 import org.collectionspace.services.client.PoxPayloadOut;
26 import org.collectionspace.services.client.ReportClient;
27 import org.collectionspace.services.client.workflow.WorkflowClient;
28 import org.collectionspace.services.common.CSWebApplicationException;
29 import org.collectionspace.services.common.ResourceMap;
30 import org.collectionspace.services.common.ServiceMain;
31 import org.collectionspace.services.common.api.RefName;
32 import org.collectionspace.services.common.config.ConfigUtils;
33 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
34 import org.collectionspace.services.common.query.UriInfoImpl;
35 import org.collectionspace.services.common.vocabulary.AuthorityResource;
37 import org.collectionspace.services.config.service.AuthorityInstanceType;
38 import org.collectionspace.services.config.service.ServiceBindingType;
39 import org.collectionspace.services.config.service.ServiceBindingType.AuthorityInstanceList;
40 import org.collectionspace.services.config.service.Term;
41 import org.collectionspace.services.config.service.TermList;
42 import org.collectionspace.services.config.tenant.TenantBindingType;
43 import org.collectionspace.services.config.types.PropertyItemType;
44 import org.collectionspace.services.config.types.PropertyType;
45 import org.collectionspace.services.jaxb.AbstractCommonList;
46 import org.collectionspace.services.jaxb.AbstractCommonList.ListItem;
47 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
48 import org.collectionspace.services.report.ReportResource;
49 import org.nuxeo.elasticsearch.ElasticSearchComponent;
50 import org.nuxeo.elasticsearch.api.ElasticSearchService;
51 import org.nuxeo.runtime.api.Framework;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
56 import java.io.InputStream;
57 import java.lang.reflect.Constructor;
59 import java.net.URLEncoder;
60 import java.nio.charset.StandardCharsets;
61 import java.nio.file.Files;
62 import java.util.Arrays;
63 import java.util.HashSet;
64 import java.util.Hashtable;
65 import java.util.List;
68 public class CSpaceResteasyBootstrap extends ResteasyBootstrap {
69 private static final Logger logger = LoggerFactory.getLogger(CSpaceResteasyBootstrap.class);
71 private static final String RESET_AUTHORITIES_PROPERTY = "org.collectionspace.services.authorities.reset";
72 private static final String RESET_ELASTICSEARCH_INDEX_PROPERTY = "org.collectionspace.services.elasticsearch.reset";
73 private static final String RESET_REPORTS_PROPERTY = "org.collectionspace.services.reports.reset";
74 private static final String RESET_BATCH_JOBS_PROPERTY = "org.collectionspace.services.batch.reset";
75 private static final String QUICK_BOOT_PROPERTY = "org.collectionspace.services.quickboot";
76 private static final String REPORT_PROPERTY = "report";
77 private static final String BATCH_PROPERTY = "batch";
80 public void contextInitialized(ServletContextEvent event) {
83 // This call to super instantiates and initializes our JAX-RS application class.
84 // The application class is org.collectionspace.services.jaxrs.CollectionSpaceJaxRsApplication.
86 logger.info("Starting up the CollectionSpace Services JAX-RS application.");
87 super.contextInitialized(event);
88 CollectionSpaceJaxRsApplication app = (CollectionSpaceJaxRsApplication)deployment.getApplication();
89 Dispatcher disp = deployment.getDispatcher();
90 disp.getDefaultContextObjects().put(ResourceMap.class, app.getResourceMap());
92 // Property can be set in the tomcat/bin/setenv.sh (or setenv.bat) file
93 String quickBoot = System.getProperty(QUICK_BOOT_PROPERTY, Boolean.FALSE.toString());
95 if (Boolean.valueOf(quickBoot) == false) {
96 // The below properties can be set in the tomcat/bin/setenv.sh (or setenv.bat) file.
97 String resetAuthsString = System.getProperty(RESET_AUTHORITIES_PROPERTY, Boolean.FALSE.toString());
98 String resetElasticsearchIndexString = System.getProperty(RESET_ELASTICSEARCH_INDEX_PROPERTY, Boolean.FALSE.toString());
99 String resetBatchJobsString = System.getProperty(RESET_BATCH_JOBS_PROPERTY, Boolean.TRUE.toString());
100 String resetReportsString = System.getProperty(RESET_REPORTS_PROPERTY, Boolean.TRUE.toString());
102 initializeAuthorities(app.getResourceMap(), Boolean.valueOf(resetAuthsString));
104 if (Boolean.valueOf(resetElasticsearchIndexString) == true) {
105 resetElasticSearchIndex();
108 if (Boolean.valueOf(resetReportsString) == true) {
112 if (Boolean.valueOf(resetBatchJobsString) == true) {
117 logger.info("CollectionSpace Services JAX-RS application started.");
118 } catch (Exception e) {
120 throw new RuntimeException(e);
126 public void contextDestroyed(ServletContextEvent event) {
127 logger.info("Shutting down the CollectionSpace Services JAX-RS application.");
128 //Do something if needed.
129 logger.info("CollectionSpace Services JAX-RS application stopped.");
132 public void resetReports() throws Exception {
133 logger.info("Resetting reports");
135 TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
136 Hashtable<String, TenantBindingType> tenantBindingsTable = tenantBindingConfigReader.getTenantBindings(false);
138 for (TenantBindingType tenantBinding : tenantBindingsTable.values()) {
139 ServiceBindingType reportServiceBinding = null;
141 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
142 if (serviceBinding.getName().toLowerCase().trim().equals(ReportClient.SERVICE_NAME)) {
143 reportServiceBinding = serviceBinding;
149 Set<String> reportNames = new HashSet<String>();
151 if (reportServiceBinding != null) {
152 for (PropertyType property : reportServiceBinding.getProperties()) {
153 for (PropertyItemType item : property.getItem()) {
154 if (item.getKey().equals(REPORT_PROPERTY)) {
155 reportNames.add(item.getValue());
161 if (reportNames.size() > 0) {
162 CSpaceTenant tenant = new CSpaceTenant(tenantBinding.getId(), tenantBinding.getName());
164 resetTenantReports(tenant, reportNames);
169 private void resetTenantReports(CSpaceTenant tenant, Set<String> reportNames) throws Exception {
170 logger.info("Resetting reports for tenant {}", tenant.getId());
172 AuthZ.get().login(tenant);
174 CollectionSpaceJaxRsApplication app = (CollectionSpaceJaxRsApplication) deployment.getApplication();
175 ResourceMap resourceMap = app.getResourceMap();
176 ReportResource reportResource = (ReportResource) resourceMap.get(ReportClient.SERVICE_NAME);
178 for (String reportName : reportNames) {
179 File reportMetadataFile = ReportResource.getReportMetadataFile(reportName);
181 if (!reportMetadataFile.exists()) {
183 "Metadata file not found for report {} at {}",
184 reportName, reportMetadataFile.getAbsolutePath());
189 String payload = new String(Files.readAllBytes(reportMetadataFile.toPath()));
190 String reportFilename = reportName + ".jrxml";
192 UriInfo uriInfo = new UriInfoImpl(
196 "pgSz=0&filename=" + URLEncoder.encode(reportFilename, StandardCharsets.UTF_8.toString()),
197 Arrays.asList((PathSegment) new PathSegmentImpl("", false))
200 AbstractCommonList list = reportResource.getList(uriInfo);
202 if (list.getTotalItems() == 0) {
203 logger.info("Adding report " + reportName);
206 reportResource.create(resourceMap, null, payload);
207 } catch(Exception e) {
208 logger.error(e.getMessage(), e);
211 for (ListItem item : list.getListItem()) {
212 String csid = AbstractCommonListUtils.ListItemGetCSID(item);
214 // Update an existing report iff:
215 // - it was created autmatically (i.e., by the SPRING_ADMIN user)
216 // - it was last updated automatically (i.e., by the SPRING_ADMIN user)
217 // - it is not soft-deleted
219 PoxPayloadOut reportPayload = reportResource.getResourceFromCsid(null, null, csid);
220 PayloadOutputPart corePart = reportPayload.getPart(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA);
222 String createdBy = corePart.asElement().selectSingleNode(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY).getText();
223 String updatedBy = corePart.asElement().selectSingleNode(CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_BY).getText();
224 String workflowState = corePart.asElement().selectSingleNode(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE).getText();
227 createdBy.equals(AuthN.SPRING_ADMIN_USER)
228 && updatedBy.equals(AuthN.SPRING_ADMIN_USER)
229 && !workflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)
231 logger.info("Updating report {} with csid {}", reportName, csid);
234 reportResource.update(resourceMap, null, csid, payload);
235 } catch (Exception e) {
236 logger.error(e.getMessage(), e);
240 "Not updating report {} with csid {} - it was not auto-created, or was updated or soft-deleted",
248 public void resetBatchJobs() throws Exception {
249 logger.info("Resetting batch jobs");
251 TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
252 Hashtable<String, TenantBindingType> tenantBindingsTable = tenantBindingConfigReader.getTenantBindings(false);
254 for (TenantBindingType tenantBinding : tenantBindingsTable.values()) {
255 ServiceBindingType batchServiceBinding = null;
257 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
258 if (serviceBinding.getName().toLowerCase().trim().equals(BatchClient.SERVICE_NAME)) {
259 batchServiceBinding = serviceBinding;
265 Set<String> batchNames = new HashSet<String>();
267 if (batchServiceBinding != null) {
268 for (PropertyType property : batchServiceBinding.getProperties()) {
269 for (PropertyItemType item : property.getItem()) {
270 if (item.getKey().equals(BATCH_PROPERTY)) {
271 batchNames.add(item.getValue());
277 if (batchNames.size() > 0) {
278 CSpaceTenant tenant = new CSpaceTenant(tenantBinding.getId(), tenantBinding.getName());
280 resetTenantBatchJobs(tenant, batchNames);
285 private void resetTenantBatchJobs(CSpaceTenant tenant, Set<String> batchNames) throws Exception {
286 logger.info("Resetting batch jobs for tenant {}", tenant.getId());
288 AuthZ.get().login(tenant);
290 CollectionSpaceJaxRsApplication app = (CollectionSpaceJaxRsApplication) deployment.getApplication();
291 ResourceMap resourceMap = app.getResourceMap();
292 BatchResource batchResource = (BatchResource) resourceMap.get(BatchClient.SERVICE_NAME);
294 for (String batchName : batchNames) {
295 InputStream batchMetadataInputStream = BatchResource.getBatchMetadataInputStream(batchName);
297 if (batchMetadataInputStream == null) {
299 "Metadata file not found for batch {}", batchName);
304 String payload = IOUtils.toString(batchMetadataInputStream, StandardCharsets.UTF_8);
306 batchMetadataInputStream.close();
308 UriInfo uriInfo = new UriInfoImpl(
312 "pgSz=0&classname=" + URLEncoder.encode(batchName, StandardCharsets.UTF_8.toString()),
313 Arrays.asList((PathSegment) new PathSegmentImpl("", false))
316 AbstractCommonList list = batchResource.getList(uriInfo);
318 if (list.getTotalItems() == 0) {
319 logger.info("Adding batch job " + batchName);
322 batchResource.create(resourceMap, null, payload);
323 } catch(Exception e) {
324 logger.error(e.getMessage(), e);
327 for (ListItem item : list.getListItem()) {
328 String csid = AbstractCommonListUtils.ListItemGetCSID(item);
330 // Update an existing batch job iff:
331 // - it was created autmatically (i.e., by the SPRING_ADMIN user)
332 // - it was last updated automatically (i.e., by the SPRING_ADMIN user)
333 // - it is not soft-deleted
335 PoxPayloadOut batchPayload = batchResource.getResourceFromCsid(null, null, csid);
336 PayloadOutputPart corePart = batchPayload.getPart(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA);
338 String createdBy = corePart.asElement().selectSingleNode(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY).getText();
339 String updatedBy = corePart.asElement().selectSingleNode(CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_BY).getText();
340 String workflowState = corePart.asElement().selectSingleNode(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE).getText();
343 createdBy.equals(AuthN.SPRING_ADMIN_USER)
344 && updatedBy.equals(AuthN.SPRING_ADMIN_USER)
345 && !workflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)
347 logger.info("Updating batch job {} with csid {}", batchName, csid);
350 batchResource.update(resourceMap, null, csid, payload);
351 } catch (Exception e) {
352 logger.error(e.getMessage(), e);
356 "Not updating batch job {} with csid {} - it was not auto-created, or was updated or soft-deleted",
364 public void resetElasticSearchIndex() throws Exception {
365 boolean isEnabled = Boolean.parseBoolean(Framework.getProperty(ES_ENABLED_PROPERTY, "true"));
371 ElasticSearchComponent es = (ElasticSearchComponent) Framework.getService(ElasticSearchService.class);
373 for (String repositoryName : es.getRepositoryNames()) {
374 logger.info("Rebuilding Elasticsearch index for repository {}", repositoryName);
376 es.dropAndInitRepositoryIndex(repositoryName);
379 TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
380 Hashtable<String, TenantBindingType> tenantBindingsTable = tenantBindingConfigReader.getTenantBindings(false);
382 for (TenantBindingType tenantBinding : tenantBindingsTable.values()) {
383 CSpaceTenant tenant = new CSpaceTenant(tenantBinding.getId(), tenantBinding.getName());
385 AuthZ.get().login(tenant);
387 for (ServiceBindingType serviceBinding : tenantBinding.getServiceBindings()) {
388 Boolean isElasticsearchIndexed = serviceBinding.isElasticsearchIndexed();
389 String servicesRepoDomainName = serviceBinding.getRepositoryDomain();
391 if (isElasticsearchIndexed && servicesRepoDomainName != null && servicesRepoDomainName.trim().isEmpty() == false) {
392 String repositoryName = ConfigUtils.getRepositoryName(tenantBinding, servicesRepoDomainName);
393 String docType = NuxeoUtils.getTenantQualifiedDocType(tenantBinding.getId(), serviceBinding.getObject().getName());
395 logger.info("Starting Elasticsearch reindexing for docType {} in repository {}", docType, repositoryName);
397 es.runReindexingWorker(repositoryName, String.format("SELECT ecm:uuid FROM %s", docType));
404 * Initialize all authorities and vocabularies defined in the service bindings.
408 public void initializeAuthorities(ResourceMap resourceMap, boolean reset) throws Exception {
409 TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
410 Hashtable<String, TenantBindingType> tenantBindingsTable = tenantBindingConfigReader.getTenantBindings(false);
411 for (TenantBindingType tenantBindings : tenantBindingsTable.values()) {
412 CSpaceTenant tenant = new CSpaceTenant(tenantBindings.getId(), tenantBindings.getName());
413 if (shouldInitializeAuthorities(tenant, reset) == true) {
414 logger.info("Initializing vocabularies and authorities of tenant '{}'.", tenant.getId());
415 for (ServiceBindingType serviceBinding : tenantBindings.getServiceBindings()) {
416 AuthorityInstanceList element = serviceBinding.getAuthorityInstanceList();
417 if (element != null && element.getAuthorityInstance() != null) {
418 List<AuthorityInstanceType> authorityInstanceList = element.getAuthorityInstance();
419 for (AuthorityInstanceType authorityInstance : authorityInstanceList) {
421 initializeAuthorityInstance(resourceMap, authorityInstance, serviceBinding, tenant, reset);
422 } catch (Exception e) {
423 logger.error("Could not initialize authorities and authority terms: " + e.getMessage());
430 // If we made it this far, we've either created the tenant's authorities and terms or we've reset them. Either way,
431 // we should mark the isAuthoritiesInitialized field of the tenant to 'true'.
433 setAuthoritiesInitialized(tenant, true);
438 @SuppressWarnings("rawtypes")
439 private AuthorityClient getAuthorityClient(String classname) throws Exception {
440 Class clazz = Class.forName(classname.trim());
441 Constructor co = clazz.getConstructor(null);
442 Object classInstance = co.newInstance(null);
443 return (AuthorityClient) classInstance;
446 private boolean shouldInitializeAuthorities(CSpaceTenant cspaceTenant, boolean reset) {
447 AuthZ.get().login(); // login as super admin
448 TenantResource tenantResource = new TenantResource();
449 Tenant tenantState = tenantResource.getTenant(cspaceTenant.getId());
452 // If the tenant's authorities have been initialized and
453 // we're not being asked to reset them, we'll return 'false'
454 // making any changes
456 return tenantState.isAuthoritiesInitialized() == false || reset == true;
459 private void setAuthoritiesInitialized(CSpaceTenant cspaceTenant, boolean initState) {
460 AuthZ.get().login(); // login as super admin
461 TenantResource tenantResource = new TenantResource();
462 Tenant tenantState = tenantResource.getTenant(cspaceTenant.getId());
464 tenantState.setAuthoritiesInitialized(initState);
465 tenantResource.updateTenant(cspaceTenant.getId(), tenantState);
470 * Check to see if an an authority instance and its corresponding terms exist. If not, try to create them.
472 private void initializeAuthorityInstance(ResourceMap resourceMap,
473 AuthorityInstanceType authorityInstance,
474 ServiceBindingType serviceBinding,
475 CSpaceTenant cspaceTenant,
476 boolean reset) throws Exception {
478 Response response = null;
479 String serviceName = serviceBinding.getName();
481 AuthZ.get().login(cspaceTenant);
482 String clientClassName = serviceBinding.getClientHandler();
483 AuthorityClient client = getAuthorityClient(clientClassName);
484 String authoritySpecifier = RefName.shortIdToPath(authorityInstance.getTitleRef()); // e.g., urn:cspace:name(ulan)
487 // Test to see if the authority instance exists already.
489 AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(serviceName.toLowerCase());
491 response = authorityResource.get(null, null, null, authoritySpecifier);
492 } catch (CSWebApplicationException e) {
493 response = e.getResponse(); // If the authority doesn't exist, we expect a 404 error
497 // If it doesn't exist (status is not 200), then try to create the authority instance
499 status = response.getStatus();
500 if (status != Response.Status.OK.getStatusCode()) {
501 String xmlPayload = client.createAuthorityInstance(authorityInstance.getTitleRef(), authorityInstance.getTitle());
502 response = authorityResource.createAuthority(xmlPayload);
503 status = response.getStatus();
504 if (status != Response.Status.CREATED.getStatusCode()) {
505 throw new CSWebApplicationException(response);
509 if (status == Response.Status.OK.getStatusCode()) {
510 logger.debug("Authority of type '{}' with the short ID of '{}' existed already.",
511 serviceName, authorityInstance.getTitleRef());
512 } else if (status == Response.Status.CREATED.getStatusCode()) {
513 logger.debug("Created a new authority of type '{}' with the short ID of '{}'.",
514 serviceName, authorityInstance.getTitleRef());
516 logger.warn("Unknown status '{}' encountered when creating or fetching authority of type '{}' with the short ID of '{}'.",
517 status, serviceName, authorityInstance.getTitleRef());
521 // Next, try to create or verify the authority terms.
523 initializeAuthorityInstanceTerms(authorityResource, client, authoritySpecifier, resourceMap, authorityInstance, serviceName, cspaceTenant);
526 private void initializeAuthorityInstanceTerms(
527 AuthorityResource authorityResource,
528 AuthorityClient client,
529 String authoritySpecifier,
530 ResourceMap resourceMap,
531 AuthorityInstanceType authorityInstance,
533 CSpaceTenant tenant) throws Exception {
536 Response response = null;
538 TermList termListElement = authorityInstance.getTermList();
539 if (termListElement == null) {
543 for (Term term : termListElement.getTerm()) {
545 // Check to see if the term already exists
548 String termSpecifier = RefName.shortIdToPath(term.getId());
549 authorityResource.getAuthorityItem(null, null, resourceMap, authoritySpecifier, termSpecifier);
550 status = Response.Status.OK.getStatusCode();
551 } catch (CSWebApplicationException e) {
552 response = e.getResponse(); // If the authority doesn't exist, we expect a 404 error
553 status = response.getStatus();
557 // If the term doesn't exist, create it.
559 if (status != Response.Status.OK.getStatusCode()) {
560 String termShortId = term.getId();
561 String termDisplayName = term.getContent().trim();
562 String xmlPayload = client.createAuthorityItemInstance(termShortId, termDisplayName);
564 authorityResource.createAuthorityItem(resourceMap, null, authoritySpecifier, xmlPayload);
565 logger.debug("Tenant:{}:Created a new term '{}:{}' in the authority of type '{}' with the short ID of '{}'.",
566 tenant.getName(), termDisplayName, termShortId, serviceName, authorityInstance.getTitleRef());
567 } catch (CSWebApplicationException e) {
568 response = e.getResponse();
569 status = response.getStatus();
570 if (status != Response.Status.CREATED.getStatusCode()) {
571 throw new CSWebApplicationException(response);