/** * 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.dataloader; import static org.opencastproject.assetmanager.api.AssetManager.DEFAULT_OWNER; import org.opencastproject.assetmanager.api.AssetManager; import org.opencastproject.job.api.Job; import org.opencastproject.job.api.Job.Status; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.mediapackage.MediaPackageElement; import org.opencastproject.mediapackage.MediaPackageElementFlavor; import org.opencastproject.mediapackage.MediaPackageElements; import org.opencastproject.mediapackage.MediaPackageParser; import org.opencastproject.mediapackage.Track; import org.opencastproject.mediapackage.identifier.IdImpl; import org.opencastproject.metadata.dublincore.DublinCoreCatalog; import org.opencastproject.metadata.dublincore.DublinCores; import org.opencastproject.metadata.dublincore.OpencastDctermsDublinCore; import org.opencastproject.metadata.dublincore.OpencastDctermsDublinCore.Series; import org.opencastproject.scheduler.api.SchedulerService; import org.opencastproject.security.api.AccessControlEntry; import org.opencastproject.security.api.AccessControlList; import org.opencastproject.security.api.AclScope; import org.opencastproject.security.api.AuthorizationService; import org.opencastproject.security.api.DefaultOrganization; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.Permissions; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.UnauthorizedException; import org.opencastproject.security.api.User; import org.opencastproject.security.util.SecurityUtil; import org.opencastproject.series.api.SeriesException; import org.opencastproject.series.api.SeriesService; import org.opencastproject.serviceregistry.api.ServiceRegistry; import org.opencastproject.util.Checksum; import org.opencastproject.util.ChecksumType; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.data.Effect0; import org.opencastproject.util.data.Tuple; import org.opencastproject.workflow.api.WorkflowDefinition; import org.opencastproject.workflow.api.WorkflowInstance; import org.opencastproject.workflow.api.WorkflowInstance.WorkflowState; import org.opencastproject.workflow.api.WorkflowInstanceImpl; import org.opencastproject.workflow.api.WorkflowParser; import org.opencastproject.workflow.api.WorkflowService; import org.opencastproject.workspace.api.Workspace; import com.entwinemedia.fn.data.Opt; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.joda.time.DateTime; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * A data loader to populate events provider with sample data for testing scalability. */ public class EventsLoader { /** The logger */ protected static final Logger logger = LoggerFactory.getLogger(EventsLoader.class); /** The series service */ protected SeriesService seriesService = null; /** The workflow service */ protected WorkflowService workflowService = null; /** The scheduler service */ protected SchedulerService schedulerService = null; protected AssetManager assetManager = null; /** The security service */ protected SecurityService securityService = null; /** The service registry */ protected ServiceRegistry serviceRegistry = null; /** The authorization service */ protected AuthorizationService authorizationService = null; /** The workspace */ protected Workspace workspace = null; private final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); private String systemUserName; private final AccessControlList defaultAcl = new AccessControlList( new AccessControlEntry("ROLE_ADMIN", Permissions.Action.WRITE.toString(), true), new AccessControlEntry("ROLE_ADMIN", Permissions.Action.READ.toString(), true), new AccessControlEntry("ROLE_USER", Permissions.Action.READ.toString(), true)); /** * Callback on component activation. */ protected void activate(ComponentContext cc) throws Exception { boolean loadTestData = BooleanUtils .toBoolean(cc.getBundleContext().getProperty("org.opencastproject.dataloader.testdata")); String csvPath = StringUtils.trimToNull(cc.getBundleContext().getProperty("org.opencastproject.dataloader.csv")); systemUserName = cc.getBundleContext().getProperty(SecurityUtil.PROPERTY_KEY_SYS_USER); File csv = new File(csvPath); // Load the demo users, if necessary if (loadTestData && csv.exists() && serviceRegistry.count(null, null) == 0) { // Load events by CSV file new Loader(csv).start(); } } private void addArchiveEntry(final MediaPackage mediaPackage) { final User user = securityService.getUser(); final Organization organization = securityService.getOrganization(); singleThreadExecutor.execute(new Runnable() { @Override public void run() { SecurityUtil.runAs(securityService, organization, user, new Effect0() { @Override protected void run() { assetManager.takeSnapshot(DEFAULT_OWNER, mediaPackage); } }); } }); } private void addWorkflowEntry(MediaPackage mediaPackage) throws Exception { WorkflowDefinition def = workflowService.getWorkflowDefinitionById("full"); WorkflowInstance workflowInstance = new WorkflowInstanceImpl(def, mediaPackage, null, securityService.getUser(), securityService.getOrganization(), new HashMap<String, String>()); workflowInstance.setState(WorkflowState.SUCCEEDED); String xml = WorkflowParser.toXml(workflowInstance); // create job Job job = serviceRegistry.createJob(WorkflowService.JOB_TYPE, "START_WORKFLOW", null, null, false); job.setStatus(Status.FINISHED); job.setPayload(xml); job = serviceRegistry.updateJob(job); workflowInstance.setId(job.getId()); workflowService.update(workflowInstance); } private void addSchedulerEntry(EventEntry event, DublinCoreCatalog episodeDublinCore) throws Exception { schedulerService.addEvent(episodeDublinCore, new HashMap<String, String>()); } private void execute(CSVParser csv) throws Exception { List<EventEntry> events = parseCSV(csv); logger.info("Found {} events to populate", events.size()); int i = 1; final Date now = new Date(); for (EventEntry event : events) { logger.info("Populating event {}", i); createSeries(event); Tuple<MediaPackage, DublinCoreCatalog> basicMediaPackage = getBasicMediaPackage(event); if (now.after(event.getRecordingDate())) { addWorkflowEntry(basicMediaPackage.getA()); if (event.isArchive()) addArchiveEntry(basicMediaPackage.getA()); } else { addSchedulerEntry(event, basicMediaPackage.getB()); } logger.info("Finished populating event {}", i++); } } private Tuple<MediaPackage, DublinCoreCatalog> getBasicMediaPackage(EventEntry event) throws Exception { URL baseMediapackageUrl = EventsLoader.class.getResource("/base_mediapackage.xml"); MediaPackage mediaPackage = MediaPackageParser.getFromXml(IOUtils.toString(baseMediapackageUrl)); DublinCoreCatalog episodeDublinCore = getBasicEpisodeDublinCore(event); mediaPackage.setDate(event.getRecordingDate()); mediaPackage.setIdentifier(new IdImpl(episodeDublinCore.getFirst(DublinCoreCatalog.PROPERTY_IDENTIFIER))); mediaPackage.setTitle(event.getTitle()); addDublinCoreCatalog(IOUtils.toInputStream(episodeDublinCore.toXmlString(), "UTF-8"), MediaPackageElements.EPISODE, mediaPackage); // assign to a series if (event.getSeries().isSome()) { DublinCoreCatalog seriesCatalog = seriesService.getSeries(event.getSeries().get()); mediaPackage.setSeries(event.getSeries().get()); mediaPackage.setSeriesTitle(seriesCatalog.getFirst(DublinCoreCatalog.PROPERTY_TITLE)); addDublinCoreCatalog(IOUtils.toInputStream(seriesCatalog.toXmlString(), "UTF-8"), MediaPackageElements.SERIES, mediaPackage); AccessControlList acl = seriesService.getSeriesAccessControl(event.getSeries().get()); if (acl != null) { authorizationService.setAcl(mediaPackage, AclScope.Series, acl); } } // Set track URI's to demo file for (Track track : mediaPackage.getTracks()) { InputStream in = null; try { in = getClass().getResourceAsStream("/av.mov"); URI uri = workspace.put(mediaPackage.getIdentifier().compact(), track.getIdentifier(), FilenameUtils.getName(track.toString()), in); track.setURI(uri); track.setChecksum(Checksum.create(ChecksumType.DEFAULT_TYPE, getClass().getResourceAsStream("/av.mov"))); } finally { IOUtils.closeQuietly(in); } } return Tuple.tuple(mediaPackage, episodeDublinCore); } private void createSeries(EventEntry event) throws SeriesException, UnauthorizedException, NotFoundException { if (event.getSeries().isNone()) return; try { // Test if the series already exist, it does not create it. seriesService.getSeries(event.getSeries().get()); } catch (NotFoundException e) { Series catalog = DublinCores.mkOpencastSeries(event.getSeries().get()); catalog.updateTitle(event.getSeriesName().toOpt()); // If the series does not exist, we create it. seriesService.updateSeries(catalog.getCatalog()); seriesService.updateAccessControl(event.getSeries().get(), defaultAcl); } } private DublinCoreCatalog getBasicEpisodeDublinCore(EventEntry event) throws IOException { OpencastDctermsDublinCore.Episode catalog = DublinCores.mkOpencastEpisode(Opt.<String> none()); catalog.setTitle(event.getTitle()); catalog.setSpatial(event.getCaptureAgent()); catalog.setSource(event.getSource()); catalog.setCreated(event.getRecordingDate()); catalog.setContributor(event.getContributor()); catalog.setCreators(event.getPresenters()); catalog.updateDescription(event.getDescription().toOpt()); catalog.setTemporal(event.getRecordingDate(), new DateTime(event.getRecordingDate().getTime()).plusMinutes(event.getDuration()).toDate()); catalog.updateIsPartOf(event.getSeries().toOpt()); return catalog.getCatalog(); } private List<EventEntry> parseCSV(CSVParser csv) { List<EventEntry> arrayList = new ArrayList<>(); for (CSVRecord record : csv) { String title = record.get(0); String description = StringUtils.trimToNull(record.get(1)); String series = StringUtils.trimToNull(record.get(2)); String seriesName = StringUtils.trimToNull(record.get(3)); Integer days = Integer.parseInt(record.get(4)); float signum = Math.signum(days); DateTime now = DateTime.now(); if (signum > 0) { now = now.plusDays(days); } else if (signum < 0) { now = now.minusDays(days * -1); } Integer duration = Integer.parseInt(record.get(5)); boolean archive = BooleanUtils.toBoolean(record.get(6)); String agent = StringUtils.trimToNull(record.get(7)); String source = StringUtils.trimToNull(record.get(8)); String contributor = StringUtils.trimToNull(record.get(9)); List<String> presenters = Arrays.asList(StringUtils.split(StringUtils.trimToEmpty(record.get(10)), ",")); EventEntry eventEntry = new EventEntry(title, now.toDate(), duration, archive, series, agent, source, contributor, description, seriesName, presenters); arrayList.add(eventEntry); } return arrayList; } protected class Loader extends Thread { private CSVParser csvParser; public Loader(File csvData) throws IOException { try { logger.info("Reading event test data from csv {}...", csvData); csvParser = CSVParser.parse(csvData, Charset.forName("UTF-8"), CSVFormat.RFC4180); } catch (IOException e) { logger.error("Unable to parse CSV data from {}", csvData); throw e; } } @Override public void run() { Organization org = new DefaultOrganization(); User createSystemUser = SecurityUtil.createSystemUser(systemUserName, org); SecurityUtil.runAs(securityService, org, createSystemUser, new Effect0() { @Override protected void run() { try { logger.info("Start populating event test data..."); execute(csvParser); logger.info("Finished populating event test data"); } catch (Exception e) { logger.error("Unable to populate event test data: {}", ExceptionUtils.getStackTrace(e)); } } }); } } private MediaPackage addDublinCoreCatalog(InputStream in, MediaPackageElementFlavor flavor, MediaPackage mediaPackage) throws IOException { try { String elementId = UUID.randomUUID().toString(); URI catalogUrl = workspace.put(mediaPackage.getIdentifier().compact(), elementId, "dublincore.xml", in); logger.info("Adding catalog with flavor {} to mediapackage {}", flavor, mediaPackage); MediaPackageElement mpe = mediaPackage.add(catalogUrl, MediaPackageElement.Type.Catalog, flavor); mpe.setIdentifier(elementId); mpe.setChecksum(Checksum.create(ChecksumType.DEFAULT_TYPE, workspace.get(catalogUrl))); return mediaPackage; } catch (NotFoundException e) { throw new RuntimeException(e); } } /** * @param seriesService * the seriesService to set */ public void setSeriesService(SeriesService seriesService) { this.seriesService = seriesService; } /** * @param schedulerService * the schedulerService to set */ public void setSchedulerService(SchedulerService schedulerService) { this.schedulerService = schedulerService; } /** * @param assetManager * the asset manager to set */ public void setAssetManager(AssetManager assetManager) { this.assetManager = assetManager; } /** * @param securityService * the securityService to set */ public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * @param workflowService * the workflowService to set */ public void setWorkflowService(WorkflowService workflowService) { this.workflowService = workflowService; } /** * @param serviceRegistry * the serviceRegistry to set */ public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; } /** * @param authorizationService * the authorizationService to set */ public void setAuthorizationService(AuthorizationService authorizationService) { this.authorizationService = authorizationService; } /** * @param workspace * the workspace to set */ public void setWorkspace(Workspace workspace) { this.workspace = workspace; } }