Java tutorial
/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.index.service.impl; import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IDENTIFIER; import org.opencastproject.archive.api.HttpMediaPackageElementProvider; import org.opencastproject.archive.opencast.OpencastArchive; import org.opencastproject.archive.opencast.OpencastQueryBuilder; import org.opencastproject.archive.opencast.OpencastResultSet; import org.opencastproject.authorization.xacml.manager.api.AclService; import org.opencastproject.authorization.xacml.manager.api.AclServiceFactory; import org.opencastproject.authorization.xacml.manager.api.ManagedAcl; import org.opencastproject.capture.CaptureParameters; import org.opencastproject.capture.admin.api.CaptureAgentStateService; import org.opencastproject.index.service.api.IndexService; import org.opencastproject.index.service.catalog.adapter.AbstractMetadataCollection; import org.opencastproject.index.service.catalog.adapter.DublinCoreMetadataUtil; import org.opencastproject.index.service.catalog.adapter.MetadataField; import org.opencastproject.index.service.catalog.adapter.MetadataList; import org.opencastproject.index.service.catalog.adapter.MetadataUtils; import org.opencastproject.index.service.catalog.adapter.events.CommonEventCatalogUIAdapter; import org.opencastproject.index.service.catalog.adapter.events.EventCatalogUIAdapter; import org.opencastproject.index.service.catalog.adapter.series.CommonSeriesCatalogUIAdapter; import org.opencastproject.index.service.catalog.adapter.series.SeriesCatalogUIAdapter; import org.opencastproject.index.service.exception.InternalServerErrorException; import org.opencastproject.index.service.exception.MetadataParsingException; import org.opencastproject.index.service.impl.index.AbstractSearchIndex; import org.opencastproject.index.service.impl.index.event.Event; import org.opencastproject.index.service.impl.index.event.EventSearchQuery; import org.opencastproject.index.service.impl.index.series.Series; import org.opencastproject.index.service.impl.index.series.SeriesSearchQuery; import org.opencastproject.index.service.util.JSONUtils; import org.opencastproject.ingest.api.IngestException; import org.opencastproject.ingest.api.IngestService; import org.opencastproject.matterhorn.search.SearchIndexException; import org.opencastproject.matterhorn.search.SearchResult; import org.opencastproject.mediapackage.Catalog; import org.opencastproject.mediapackage.EName; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.mediapackage.MediaPackageElementFlavor; import org.opencastproject.mediapackage.MediaPackageElements; import org.opencastproject.mediapackage.MediaPackageException; import org.opencastproject.mediapackage.Track; import org.opencastproject.metadata.dublincore.DCMIPeriod; import org.opencastproject.metadata.dublincore.DublinCore; import org.opencastproject.metadata.dublincore.DublinCoreCatalog; import org.opencastproject.metadata.dublincore.DublinCoreUtil; import org.opencastproject.metadata.dublincore.DublinCoreValue; import org.opencastproject.metadata.dublincore.DublinCores; import org.opencastproject.metadata.dublincore.EncodingSchemeUtils; import org.opencastproject.metadata.dublincore.Precision; import org.opencastproject.scheduler.api.SchedulerException; import org.opencastproject.scheduler.api.SchedulerService; import org.opencastproject.security.api.AccessControlList; import org.opencastproject.security.api.AccessControlParser; import org.opencastproject.security.api.AccessControlUtil; import org.opencastproject.security.api.AclScope; import org.opencastproject.security.api.AuthorizationService; import org.opencastproject.security.api.Permissions; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.UnauthorizedException; import org.opencastproject.series.api.SeriesService; import org.opencastproject.userdirectory.UserIdRoleProvider; import org.opencastproject.util.DateTimeSupport; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.XmlNamespaceBinding; import org.opencastproject.util.XmlNamespaceContext; import org.opencastproject.util.data.Function; import org.opencastproject.util.data.Function2; import org.opencastproject.util.data.Option; import org.opencastproject.util.data.Tuple; import org.opencastproject.workflow.api.WorkflowDatabaseException; import org.opencastproject.workflow.api.WorkflowException; import org.opencastproject.workflow.api.WorkflowInstance; import org.opencastproject.workflow.api.WorkflowInstance.WorkflowState; import org.opencastproject.workflow.api.WorkflowQuery; import org.opencastproject.workflow.api.WorkflowService; import org.opencastproject.workflow.api.WorkflowSet; import org.opencastproject.workspace.api.Workspace; import com.entwinemedia.fn.Fn2; import com.entwinemedia.fn.Stream; import com.entwinemedia.fn.data.Opt; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.fileupload.util.Streams; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.codehaus.jettison.json.JSONException; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.UUID; import javax.servlet.http.HttpServletRequest; public class IndexServiceImpl implements IndexService { private static final String WORKFLOW_CONFIG_PREFIX = "org.opencastproject.workflow.config."; public static final String THEME_PROPERTY_NAME = "theme"; /** The logging facility */ private static final Logger logger = LoggerFactory.getLogger(IndexServiceImpl.class); /** A parser for handling JSON documents inside the body of a request. **/ private static final JSONParser parser = new JSONParser(); private AclServiceFactory aclServiceFactory; private AuthorizationService authorizationService; private CaptureAgentStateService captureAgentStateService; private CommonEventCatalogUIAdapter eventCatalogUIAdapter; private final List<EventCatalogUIAdapter> eventCatalogUIAdapters = new ArrayList<EventCatalogUIAdapter>(); private HttpMediaPackageElementProvider httpMediaPackageElementProvider; private IngestService ingestService; private OpencastArchive opencastArchive; private SchedulerService schedulerService; private SecurityService securityService; private final List<SeriesCatalogUIAdapter> seriesCatalogUIAdapters = new ArrayList<SeriesCatalogUIAdapter>(); private SeriesCatalogUIAdapter commonSeriesCatalogUIAdapter; private SeriesService seriesService; private WorkflowService workflowService; private Workspace workspace; public AclService getAclService() { return aclServiceFactory.serviceFor(securityService.getOrganization()); } public void setAclServiceFactory(AclServiceFactory aclServiceFactory) { this.aclServiceFactory = aclServiceFactory; } public AuthorizationService getAuthorizationService() { return authorizationService; } public void setAuthorizationService(AuthorizationService authorizationService) { this.authorizationService = authorizationService; } public CaptureAgentStateService getCaptureAgentStateService() { return captureAgentStateService; } public void setCaptureAgentStateService(CaptureAgentStateService captureAgentStateService) { this.captureAgentStateService = captureAgentStateService; } /** OSGi DI. */ public void setCommonEventCatalogUIAdapter(CommonEventCatalogUIAdapter eventCatalogUIAdapter) { this.eventCatalogUIAdapter = eventCatalogUIAdapter; } public EventCatalogUIAdapter getEpisodeCatalogUIAdapter() { return eventCatalogUIAdapter; } /** OSGi DI. */ public void addCatalogUIAdapter(EventCatalogUIAdapter catalogUIAdapter) { eventCatalogUIAdapters.add(catalogUIAdapter); } /** OSGi DI. */ public void removeCatalogUIAdapter(EventCatalogUIAdapter catalogUIAdapter) { eventCatalogUIAdapters.remove(catalogUIAdapter); } public List<EventCatalogUIAdapter> getEventCatalogUIAdapters(String organization) { return Stream.$(eventCatalogUIAdapters).filter(eventOrganizationFilter._2(organization)).toList(); } private static final Fn2<EventCatalogUIAdapter, String, Boolean> eventOrganizationFilter = new Fn2<EventCatalogUIAdapter, String, Boolean>() { @Override public Boolean ap(EventCatalogUIAdapter catalogUIAdapter, String organization) { return organization.equals(catalogUIAdapter.getOrganization()); } }; public HttpMediaPackageElementProvider getHttpMediaPackageElementProvider() { return httpMediaPackageElementProvider; } public void setHttpMediaPackageElementProvider( HttpMediaPackageElementProvider httpMediaPackageElementProvider) { this.httpMediaPackageElementProvider = httpMediaPackageElementProvider; } public IngestService getIngestService() { return ingestService; } public void setIngestService(IngestService ingestService) { this.ingestService = ingestService; } public OpencastArchive getOpencastArchive() { return opencastArchive; } public void setOpencastArchive(OpencastArchive opencastArchive) { this.opencastArchive = opencastArchive; } public SchedulerService getSchedulerService() { return schedulerService; } public void setSchedulerService(SchedulerService schedulerService) { this.schedulerService = schedulerService; } public SecurityService getSecurityService() { return securityService; } public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** OSGi callback to add the series dublincore {@link SeriesCatalogUIAdapter} instance. */ public void setCommonSeriesCatalogUIAdapter(CommonSeriesCatalogUIAdapter commonSeriesCatalogUIAdapter) { this.commonSeriesCatalogUIAdapter = commonSeriesCatalogUIAdapter; } /** OSGi callback to add {@link SeriesCatalogUIAdapter} instance. */ public void addCatalogUIAdapter(SeriesCatalogUIAdapter catalogUIAdapter) { seriesCatalogUIAdapters.add(catalogUIAdapter); } /** OSGi callback to remove {@link SeriesCatalogUIAdapter} instance. */ public void removeCatalogUIAdapter(SeriesCatalogUIAdapter catalogUIAdapter) { seriesCatalogUIAdapters.remove(catalogUIAdapter); } /** * @param organization * The organization to filter the results with. * @return A {@link List} of {@link SeriesCatalogUIAdapter} that provide the metadata to the front end. */ public List<SeriesCatalogUIAdapter> getSeriesCatalogUIAdapters(String organization) { return Stream.$(seriesCatalogUIAdapters).filter(seriesOrganizationFilter._2(organization)).toList(); } private static final Fn2<SeriesCatalogUIAdapter, String, Boolean> seriesOrganizationFilter = new Fn2<SeriesCatalogUIAdapter, String, Boolean>() { @Override public Boolean ap(SeriesCatalogUIAdapter catalogUIAdapter, String organization) { return catalogUIAdapter.getOrganization().equals(organization); } }; public SeriesService getSeriesService() { return seriesService; } public void setSeriesService(SeriesService seriesService) { this.seriesService = seriesService; } public WorkflowService getWorkflowService() { return workflowService; } public void setWorkflowService(WorkflowService workflowService) { this.workflowService = workflowService; } public Workspace getWorkspace() { return workspace; } public void setWorkspace(Workspace workspace) { this.workspace = workspace; } @Override public String createEvent(HttpServletRequest request) throws InternalServerErrorException, IllegalArgumentException { JSONObject metadataJson = null; MediaPackage mp = null; try { if (ServletFileUpload.isMultipartContent(request)) { mp = getIngestService().createMediaPackage(); for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) { FileItemStream item = iter.next(); String fieldName = item.getFieldName(); if (item.isFormField()) { if ("metadata".equals(fieldName)) { String metadata = Streams.asString(item.openStream()); try { metadataJson = (JSONObject) parser.parse(metadata); } catch (Exception e) { logger.warn("Unable to parse metadata {}", metadata); throw new IllegalArgumentException("Unable to parse metadata"); } } } else { if ("presenter".equals(item.getFieldName())) { mp = getIngestService().addTrack(item.openStream(), item.getName(), MediaPackageElements.PRESENTER_SOURCE, mp); } else if ("presentation".equals(item.getFieldName())) { mp = getIngestService().addTrack(item.openStream(), item.getName(), MediaPackageElements.PRESENTATION_SOURCE, mp); } else if ("audio".equals(item.getFieldName())) { mp = getIngestService().addTrack(item.openStream(), item.getName(), new MediaPackageElementFlavor("presenter-audio", "source"), mp); } else { logger.warn("Unknown field name found {}", item.getFieldName()); } } } } else { throw new IllegalArgumentException("No multipart content"); } // MH-10834 If there is only an audio track, change the flavor from presenter-audio/source to presenter/source. if (mp.getTracks().length == 1 && mp.getTracks()[0].getFlavor() .equals(new MediaPackageElementFlavor("presenter-audio", "source"))) { Track audioTrack = mp.getTracks()[0]; mp.remove(audioTrack); audioTrack.setFlavor(MediaPackageElements.PRESENTER_SOURCE); mp.add(audioTrack); } return createEvent(metadataJson, mp); } catch (Exception e) { logger.error("Unable to create event: {}", ExceptionUtils.getStackTrace(e)); throw new InternalServerErrorException(e.getMessage()); } } protected String createEvent(JSONObject metadataJson, MediaPackage mp) throws ParseException, IOException, MediaPackageException, IngestException, NotFoundException, SchedulerException, UnauthorizedException { if (metadataJson == null) throw new IllegalArgumentException("No metadata set"); JSONObject source = (JSONObject) metadataJson.get("source"); if (source == null) throw new IllegalArgumentException("No source field in metadata"); JSONObject processing = (JSONObject) metadataJson.get("processing"); if (processing == null) throw new IllegalArgumentException("No processing field in metadata"); String workflowTemplate = (String) processing.get("workflow"); if (workflowTemplate == null) throw new IllegalArgumentException("No workflow template in metadata"); JSONArray allEventMetadataJson = (JSONArray) metadataJson.get("metadata"); if (allEventMetadataJson == null) throw new IllegalArgumentException("No metadata field in metadata"); SourceType type; try { type = SourceType.valueOf((String) source.get("type")); } catch (Exception e) { logger.error("Unknown source type '{}'", source.get("type")); throw new IllegalArgumentException("Unkown source type"); } DublinCoreCatalog dc; InputStream inputStream = null; Properties caProperties = new Properties(); try { MetadataList metadataList = getMetadatListWithAllEventCatalogUIAdapters(); metadataList.fromJSON(allEventMetadataJson.toJSONString()); AbstractMetadataCollection eventMetadata = metadataList.getMetadataByAdapter(eventCatalogUIAdapter) .get(); JSONObject sourceMetadata = (JSONObject) source.get("metadata"); if (sourceMetadata != null && (type.equals(SourceType.SCHEDULE_SINGLE) || type.equals(SourceType.SCHEDULE_MULTIPLE))) { try { MetadataField<?> current = eventMetadata.getOutputFields().get("agent"); eventMetadata.updateStringField(current, (String) sourceMetadata.get("device")); } catch (Exception e) { logger.warn("Unable to parse device {}", sourceMetadata.get("device")); throw new IllegalArgumentException("Unable to parse device"); } } MetadataField<?> created = eventMetadata.getOutputFields().get("created"); if (created == null || !created.isUpdated() || created.getValue().isNone()) { eventMetadata.removeField(created); MetadataField<String> newCreated = MetadataUtils.copyMetadataField(created); newCreated.setValue(EncodingSchemeUtils.encodeDate(new Date(), Precision.Second).getValue()); eventMetadata.addField(newCreated); } metadataList.add(eventCatalogUIAdapter, eventMetadata); updateMediaPackageMetadata(mp, metadataList); Option<DublinCoreCatalog> dcOpt = DublinCoreUtil.loadEpisodeDublinCore(getWorkspace(), mp); if (dcOpt.isSome()) { dc = dcOpt.get(); // make sure to bind the OC_PROPERTY namespace dc.addBindings(XmlNamespaceContext.mk( XmlNamespaceBinding.mk(DublinCores.OC_PROPERTY_NS_PREFIX, DublinCores.OC_PROPERTY_NS_URI))); } else { dc = DublinCores.mkOpencast(); } if (sourceMetadata != null && (type.equals(SourceType.SCHEDULE_SINGLE) || type.equals(SourceType.SCHEDULE_MULTIPLE))) { Date start = new Date(DateTimeSupport.fromUTC((String) sourceMetadata.get("start"))); Date end = new Date(DateTimeSupport.fromUTC((String) sourceMetadata.get("end"))); DublinCoreValue period = EncodingSchemeUtils.encodePeriod(new DCMIPeriod(start, end), Precision.Second); String inputs = (String) sourceMetadata.get("inputs"); Properties configuration; try { configuration = getCaptureAgentStateService() .getAgentConfiguration((String) sourceMetadata.get("device")); } catch (Exception e) { logger.warn("Unable to parse device {}: because: {}", sourceMetadata.get("device"), ExceptionUtils.getStackTrace(e)); throw new IllegalArgumentException("Unable to parse device"); } caProperties.putAll(configuration); String agentTimeZone = configuration.getProperty("capture.device.timezone.offset"); dc.set(DublinCores.OC_PROPERTY_AGENT_TIMEZONE, agentTimeZone); dc.set(DublinCore.PROPERTY_TEMPORAL, period); caProperties.put(CaptureParameters.CAPTURE_DEVICE_NAMES, inputs); } if (type.equals(SourceType.SCHEDULE_MULTIPLE)) { String rrule = (String) sourceMetadata.get("rrule"); dc.set(DublinCores.OC_PROPERTY_RECURRENCE, rrule); } inputStream = IOUtils.toInputStream(dc.toXmlString(), "UTF-8"); // Update dublincore catalog Catalog[] catalogs = mp.getCatalogs(MediaPackageElements.EPISODE); if (catalogs.length > 0) { Catalog catalog = catalogs[0]; URI uri = getWorkspace().put(mp.getIdentifier().toString(), catalog.getIdentifier(), "dublincore.xml", inputStream); catalog.setURI(uri); // setting the URI to a new source so the checksum will most like be invalid catalog.setChecksum(null); } else { mp = getIngestService().addCatalog(inputStream, "dublincore.xml", MediaPackageElements.EPISODE, mp); } } catch (MetadataParsingException e) { logger.warn("Unable to parse event metadata {}", allEventMetadataJson.toJSONString()); throw new IllegalArgumentException("Unable to parse metadata set"); } finally { IOUtils.closeQuietly(inputStream); } Map<String, String> configuration = new HashMap<String, String>( (JSONObject) processing.get("configuration")); for (Entry<String, String> entry : configuration.entrySet()) { caProperties.put(WORKFLOW_CONFIG_PREFIX.concat(entry.getKey()), entry.getValue()); } caProperties.put(CaptureParameters.INGEST_WORKFLOW_DEFINITION, workflowTemplate); AccessControlList acl = new AccessControlList(); JSONObject accessJson = (JSONObject) metadataJson.get("access"); if (accessJson != null) { try { acl = AccessControlParser.parseAcl(accessJson.toJSONString()); } catch (Exception e) { logger.warn("Unable to parse access control list: {}", accessJson.toJSONString()); throw new IllegalArgumentException("Unable to parse access control list!"); } } switch (type) { case UPLOAD: case UPLOAD_LATER: mp = getAuthorizationService().setAcl(mp, AclScope.Episode, acl).getA(); configuration.put("workflowDefinitionId", workflowTemplate); WorkflowInstance ingest = getIngestService().ingest(mp, workflowTemplate, configuration); return Long.toString(ingest.getId()); case SCHEDULE_SINGLE: getIngestService().discardMediaPackage(mp); Long id = getSchedulerService().addEvent(dc, configuration); getSchedulerService().updateCaptureAgentMetadata(caProperties, Tuple.tuple(id, dc)); getSchedulerService().updateAccessControlList(id, acl); return Long.toString(id); case SCHEDULE_MULTIPLE: getIngestService().discardMediaPackage(mp); // try to create event and it's recurrences Long[] createdIDs = getSchedulerService().addReccuringEvent(dc, configuration); for (long createdId : createdIDs) { getSchedulerService().updateCaptureAgentMetadata(caProperties, Tuple.tuple(createdId, getSchedulerService().getEventDublinCore(createdId))); getSchedulerService().updateAccessControlList(createdId, acl); } return StringUtils.join(createdIDs, ","); default: logger.warn("Unknown source type {}", type); throw new IllegalArgumentException("Unknown source type"); } } @Override public MetadataList updateCommonEventMetadata(String id, String metadataJSON, AbstractSearchIndex index) throws IllegalArgumentException, InternalServerErrorException, NotFoundException, UnauthorizedException { MetadataList metadataList; try { metadataList = getMetadatListWithCommonEventCatalogUIAdapter(); metadataList.fromJSON(metadataJSON); } catch (Exception e) { logger.warn("Not able to parse the event metadata {}: {}", metadataJSON, ExceptionUtils.getStackTrace(e)); throw new IllegalArgumentException("Not able to parse the event metadata " + metadataJSON, e); } return updateEventMetadata(id, metadataJSON, index, metadataList); } @Override public MetadataList updateAllEventMetadata(String id, String metadataJSON, AbstractSearchIndex index) throws IllegalArgumentException, InternalServerErrorException, NotFoundException, UnauthorizedException { MetadataList metadataList; try { metadataList = getMetadatListWithAllEventCatalogUIAdapters(); metadataList.fromJSON(metadataJSON); } catch (Exception e) { logger.warn("Not able to parse the event metadata {}: {}", metadataJSON, ExceptionUtils.getStackTrace(e)); throw new IllegalArgumentException("Not able to parse the event metadata " + metadataJSON, e); } return updateEventMetadata(id, metadataJSON, index, metadataList); } protected MetadataList updateEventMetadata(String id, String metadataJSON, AbstractSearchIndex index, MetadataList metadataList) throws IllegalArgumentException, InternalServerErrorException, NotFoundException, UnauthorizedException { Opt<Event> optEvent = getEvent(id, index); if (optEvent.isNone()) throw new NotFoundException("Cannot find an event with id " + id); Event event = optEvent.get(); Opt<MediaPackage> mpOpt = getEventMediapackage(event); MediaPackage mediaPackage; DublinCoreCatalog dc; switch (getEventSource(event)) { case WORKFLOW: try { if (mpOpt.isNone()) { logger.error("No mediapackage found for workflow event {}!", id); throw new InternalServerErrorException("No mediapackage found for workflow event {}!" + id); } mediaPackage = mpOpt.get(); updateMediaPackageMetadata(mediaPackage, metadataList); WorkflowInstance workflowInstance = getCurrentWorkflowInstance(event.getIdentifier()); workflowInstance.setMediaPackage(mediaPackage); updateWorkflowInstance(workflowInstance); } catch (WorkflowDatabaseException e) { logger.error("Unable to update workflow event {} with metadata {} because {}", new Object[] { id, metadataJSON, ExceptionUtils.getStackTrace(e) }); throw new InternalServerErrorException("Unable to update workflow event " + id); } catch (WorkflowException e) { logger.error("Unable to update workflow event {} with metadata {} because {}", new Object[] { id, metadataJSON, ExceptionUtils.getStackTrace(e) }); throw new InternalServerErrorException("Unable to update workflow event " + id); } break; case ARCHIVE: if (mpOpt.isNone()) { logger.error("No mediapackage found for archived event {}!", id); throw new InternalServerErrorException("No mediapackage found for archived event {}!" + id); } mediaPackage = mpOpt.get(); updateMediaPackageMetadata(mediaPackage, metadataList); getOpencastArchive().add(mediaPackage); break; case SCHEDULE: try { Long eventId = getSchedulerService().getEventId(event.getIdentifier()); dc = getSchedulerService().getEventDublinCore(eventId); Opt<AbstractMetadataCollection> abstractMetadata = metadataList .getMetadataByAdapter(getEpisodeCatalogUIAdapter()); if (abstractMetadata.isSome()) { DublinCoreMetadataUtil.updateDublincoreCatalog(dc, abstractMetadata.get()); } getSchedulerService().updateEvent(eventId, dc, new HashMap<String, String>()); } catch (SchedulerException e) { logger.error("Unable to update scheduled event {} with metadata {} because {}", new Object[] { id, metadataJSON, ExceptionUtils.getStackTrace(e) }); throw new InternalServerErrorException("Unable to update scheduled event " + id); } break; default: logger.error("Unkown event source!"); } return metadataList; } /** * Get a single event * * @param id * the mediapackage id * @return an event or none if not found wrapped in an option * @throws SearchIndexException */ @Override public Opt<Event> getEvent(String id, AbstractSearchIndex index) throws InternalServerErrorException { SearchResult<Event> result; try { result = index.getByQuery(new EventSearchQuery(getSecurityService().getOrganization().getId(), getSecurityService().getUser()).withIdentifier(id)); // If the results list if empty, we return already a response. if (result.getPageSize() == 0) { logger.debug("Didn't find event with id {}", id); return Opt.<Event>none(); } return Opt.some(result.getItems()[0].getSource()); } catch (IllegalStateException e) { logger.error("Unable to get event with id {} because {}", id, ExceptionUtils.getStackTrace(e)); throw new InternalServerErrorException(e.getMessage(), e); } catch (SearchIndexException e) { logger.error("Unable to get event with id {} because {}", id, ExceptionUtils.getStackTrace(e)); throw new InternalServerErrorException(e.getMessage(), e); } } @Override public void updateWorkflowInstance(WorkflowInstance workflowInstance) throws WorkflowException, UnauthorizedException { // Only update the workflow if the instance is in a working state if (WorkflowInstance.WorkflowState.FAILED.equals(workflowInstance.getState()) || WorkflowInstance.WorkflowState.FAILING.equals(workflowInstance.getState()) || WorkflowInstance.WorkflowState.STOPPED.equals(workflowInstance.getState()) || WorkflowInstance.WorkflowState.SUCCEEDED.equals(workflowInstance.getState())) { logger.info("Skip updating {} workflow mediapackage {} with updated comments catalog", workflowInstance.getState(), workflowInstance.getMediaPackage().getIdentifier().toString()); return; } getWorkflowService().update(workflowInstance); } @Override public Opt<MediaPackage> getEventMediapackage(Event event) throws InternalServerErrorException { switch (getEventSource(event)) { case WORKFLOW: WorkflowInstance currentWorkflowInstance = getCurrentWorkflowInstance(event.getIdentifier()); if (currentWorkflowInstance != null) { logger.debug("Found event in workflow with id {}", event.getIdentifier()); return Opt.some(currentWorkflowInstance.getMediaPackage()); } return Opt.none(); case ARCHIVE: final OpencastResultSet archiveRes = getOpencastArchive().find( OpencastQueryBuilder.query().mediaPackageId(event.getIdentifier()).onlyLastVersion(true), getHttpMediaPackageElementProvider().getUriRewriter()); if (archiveRes.size() > 0) { logger.debug("Found event in archive with id {}", event.getIdentifier()); return Opt.some(archiveRes.getItems().get(0).getMediaPackage()); } return Opt.none(); case SCHEDULE: return Opt.none(); default: throw new InternalServerErrorException("Unknown event type!"); } } /** * Determines in a very basic way what kind of source the event is * * @param event * the event * @return the source type * @throws InternalServerErrorException * Thrown if there is an issue getting the workflow for the event. */ @Override public Source getEventSource(Event event) { if (event.getWorkflowId() != null && isWorkflowActive(event.getWorkflowState())) return Source.WORKFLOW; if (event.getArchiveVersion() != null) return Source.ARCHIVE; if (event.getWorkflowId() != null) return Source.WORKFLOW; return Source.SCHEDULE; } @Override public WorkflowInstance getCurrentWorkflowInstance(String mpId) throws InternalServerErrorException { WorkflowQuery query = new WorkflowQuery().withMediaPackage(mpId); try { WorkflowSet workflowInstances = getWorkflowService().getWorkflowInstances(query); if (workflowInstances.size() == 0) { logger.info("No workflow instance found for mediapackage {}.", mpId); return null; } // Get the newest workflow instance // TODO This presuppose knowledge of the Database implementation and should be fixed sooner or later! WorkflowInstance workflowInstance = workflowInstances.getItems()[0]; for (WorkflowInstance instance : workflowInstances.getItems()) { if (instance.getId() > workflowInstance.getId()) workflowInstance = instance; } return workflowInstance; } catch (WorkflowDatabaseException e) { throw new InternalServerErrorException("Unable to get the current workflow instance for " + mpId, e); } } private MetadataList getMetadatListWithCommonEventCatalogUIAdapter() { MetadataList metadataList = new MetadataList(); metadataList.add(eventCatalogUIAdapter, eventCatalogUIAdapter.getRawFields()); return metadataList; } private MetadataList getMetadatListWithAllEventCatalogUIAdapters() { MetadataList metadataList = new MetadataList(); for (EventCatalogUIAdapter catalogUIAdapter : getEventCatalogUIAdapters()) { metadataList.add(catalogUIAdapter, catalogUIAdapter.getRawFields()); } return metadataList; } private static Function<ManagedAcl, AccessControlList> toAcl = new Function<ManagedAcl, AccessControlList>() { @Override public AccessControlList apply(ManagedAcl a) { return a.getAcl(); } }; private static Function2<String, AccessControlList, AccessControlList> extendAclWithCurrentUser = new Function2<String, AccessControlList, AccessControlList>() { @Override public AccessControlList apply(String username, AccessControlList acl) { String userIdRole = UserIdRoleProvider.getUserIdRole(username); acl = AccessControlUtil.extendAcl(acl, userIdRole, Permissions.Action.READ.toString(), true); acl = AccessControlUtil.extendAcl(acl, userIdRole, Permissions.Action.WRITE.toString(), true); return acl; } }; private List<EventCatalogUIAdapter> getEventCatalogUIAdapters() { return new ArrayList<EventCatalogUIAdapter>( getEventCatalogUIAdapters(getSecurityService().getOrganization().getId())); } @Override public void updateMediaPackageMetadata(MediaPackage mp, MetadataList metadataList) { List<EventCatalogUIAdapter> catalogUIAdapters = getEventCatalogUIAdapters(); if (catalogUIAdapters.size() > 0) { for (EventCatalogUIAdapter catalogUIAdapter : catalogUIAdapters) { Opt<AbstractMetadataCollection> metadata = metadataList.getMetadataByAdapter(catalogUIAdapter); if (metadata.isSome() && metadata.get().isUpdated()) { catalogUIAdapter.storeFields(mp, metadata.get()); } } } } @Override public String createSeries(String metadata) throws IllegalArgumentException, InternalServerErrorException, UnauthorizedException { JSONObject metadataJson = null; try { metadataJson = (JSONObject) new JSONParser().parse(metadata); } catch (Exception e) { logger.warn("Unable to parse metadata {}", metadata); throw new IllegalArgumentException("Unable to parse metadata" + metadata); } if (metadataJson == null) throw new IllegalArgumentException("No metadata set to create series"); JSONArray seriesMetadataJson = (JSONArray) metadataJson.get("metadata"); if (seriesMetadataJson == null) throw new IllegalArgumentException("No metadata field in metadata"); JSONObject options = (JSONObject) metadataJson.get("options"); if (options == null) throw new IllegalArgumentException("No options field in metadata"); Opt<Long> themeId = Opt.<Long>none(); Long theme = (Long) metadataJson.get("theme"); if (theme != null) { themeId = Opt.some(theme); } Map<String, String> optionsMap; try { optionsMap = JSONUtils.toMap(new org.codehaus.jettison.json.JSONObject(options.toJSONString())); } catch (JSONException e) { logger.warn("Unable to parse options to map: {}", ExceptionUtils.getStackTrace(e)); throw new IllegalArgumentException("Unable to parse options to map"); } DublinCoreCatalog dc = DublinCores.mkOpencast(); dc.set(PROPERTY_IDENTIFIER, UUID.randomUUID().toString()); dc.set(DublinCore.PROPERTY_CREATED, EncodingSchemeUtils.encodeDate(new Date(), Precision.Second)); for (Entry<String, String> entry : optionsMap.entrySet()) { dc.set(new EName(DublinCores.OC_PROPERTY_NS_URI, entry.getKey()), entry.getValue()); } MetadataList metadataList; try { metadataList = getMetadataListWithAllSeriesCatalogUIAdapters(); metadataList.fromJSON(seriesMetadataJson.toJSONString()); } catch (Exception e) { logger.warn("Not able to parse the series metadata {}: {}", seriesMetadataJson, ExceptionUtils.getStackTrace(e)); throw new IllegalArgumentException("Not able to parse the series metadata"); } Opt<AbstractMetadataCollection> seriesMetadata = metadataList .getMetadataByFlavor(MediaPackageElements.SERIES.toString()); if (seriesMetadata.isSome()) { DublinCoreMetadataUtil.updateDublincoreCatalog(dc, seriesMetadata.get()); } AccessControlList acl = new AccessControlList(); JSONObject access = (JSONObject) metadataJson.get("access"); if (access != null) { try { acl = AccessControlParser.parseAcl(access.toJSONString()); } catch (Exception e) { logger.warn("Unable to parse access control list: {}", access.toJSONString()); throw new IllegalArgumentException("Unable to parse access control list!"); } } String seriesId; try { DublinCoreCatalog createdSeries = seriesService.updateSeries(dc); seriesId = createdSeries.getFirst(PROPERTY_IDENTIFIER); seriesService.updateAccessControl(seriesId, acl); for (Long id : themeId) seriesService.updateSeriesProperty(seriesId, THEME_PROPERTY_NAME, Long.toString(id)); } catch (Exception e) { logger.error("Unable to create new series: {}", ExceptionUtils.getStackTrace(e)); throw new InternalServerErrorException("Unable to create new series"); } updateSeriesMetadata(seriesId, metadataList); return seriesId; } @Override public Opt<Series> getSeries(String seriesId, AbstractSearchIndex searchIndex) throws SearchIndexException { SearchResult<Series> result = searchIndex.getByQuery( new SeriesSearchQuery(securityService.getOrganization().getId(), securityService.getUser()) .withIdentifier(seriesId)); // If the results list if empty, we return already a response. if (result.getPageSize() == 0) { logger.debug("Didn't find series with id {}", seriesId); return Opt.<Series>none(); } return Opt.some(result.getItems()[0].getSource()); } @Override public MetadataList updateCommonSeriesMetadata(String id, String metadataJSON, AbstractSearchIndex index) throws IllegalArgumentException, InternalServerErrorException, NotFoundException, UnauthorizedException { MetadataList metadataList = getMetadataListWithCommonSeriesCatalogUIAdapters(); return updateSeriesMetadata(id, metadataJSON, index, metadataList); } @Override public MetadataList updateAllSeriesMetadata(String id, String metadataJSON, AbstractSearchIndex index) throws IllegalArgumentException, InternalServerErrorException, NotFoundException, UnauthorizedException { MetadataList metadataList = getMetadataListWithAllSeriesCatalogUIAdapters(); return updateSeriesMetadata(id, metadataJSON, index, metadataList); } public MetadataList updateSeriesMetadata(String seriesID, String metadataJSON, AbstractSearchIndex index, MetadataList metadataList) throws IllegalArgumentException, InternalServerErrorException, NotFoundException, UnauthorizedException { try { Opt<Series> optSeries = getSeries(seriesID, index); if (optSeries.isNone()) throw new NotFoundException("Cannot find a series with id " + seriesID); } catch (SearchIndexException e) { logger.error("Unable to get a series with id {} because: {}", seriesID, ExceptionUtils.getStackTrace(e)); throw new InternalServerErrorException("Cannot use search service to find Series"); } try { metadataList.fromJSON(metadataJSON); } catch (Exception e) { logger.warn("Not able to parse the event metadata {}: {}", metadataJSON, ExceptionUtils.getStackTrace(e)); throw new IllegalArgumentException("Not able to parse the event metadata"); } updateSeriesMetadata(seriesID, metadataList); return metadataList; } /** * @return A {@link MetadataList} with only the common SeriesCatalogUIAdapter's empty * {@link AbstractMetadataCollection} available */ private MetadataList getMetadataListWithCommonSeriesCatalogUIAdapters() { MetadataList metadataList = new MetadataList(); metadataList.add(commonSeriesCatalogUIAdapter.getFlavor(), commonSeriesCatalogUIAdapter.getUITitle(), commonSeriesCatalogUIAdapter.getRawFields()); return metadataList; } /** * @return A {@link MetadataList} with all of the available CatalogUIAdapters empty {@link AbstractMetadataCollection} * available */ private MetadataList getMetadataListWithAllSeriesCatalogUIAdapters() { MetadataList metadataList = new MetadataList(); for (SeriesCatalogUIAdapter adapter : getSeriesCatalogUIAdapters( securityService.getOrganization().getId())) { metadataList.add(adapter.getFlavor(), adapter.getUITitle(), adapter.getRawFields()); } return metadataList; } /** * Checks the list of metadata for updated fields and stores/updates them in the respective metadata catalog. * * @param seriesId * The series identifier * @param metadataList * The metadata list */ private void updateSeriesMetadata(String seriesId, MetadataList metadataList) { for (SeriesCatalogUIAdapter adapter : seriesCatalogUIAdapters) { Opt<AbstractMetadataCollection> metadata = metadataList.getMetadataByFlavor(adapter.getFlavor()); if (metadata.isSome() && metadata.get().isUpdated()) { adapter.storeFields(seriesId, metadata.get()); } } } public boolean isWorkflowActive(String workflowState) { if (WorkflowState.INSTANTIATED.toString().equals(workflowState) || WorkflowState.RUNNING.toString().equals(workflowState) || WorkflowState.PAUSED.toString().equals(workflowState)) { return true; } return false; } }