/* MyMAM - Open Source Digital Media Asset Management.
* http://www.mymam.net
*
* Copyright 2013, MyMAM contributors as indicated by the @author tag.
*
* Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0
*
* 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 net.mymam.ejb;
import net.mymam.data.json.*;
import net.mymam.entity.*;
import net.mymam.entity.FileProcessorTask;
import net.mymam.entity.MediaFile;
import net.mymam.exceptions.NoSuchTaskException;
import net.mymam.exceptions.NotFoundException;
import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.validation.*;
import java.util.*;
/**
* @author fstab
*/
@Stateless
@DeclareRoles({ SecurityRoles.USER, SecurityRoles.ADMIN, SecurityRoles.SYSTEM })
public class MediaFileEJB {
@PersistenceContext(unitName = "defaultPersistenceUnit")
private EntityManager em;
@Resource
SessionContext sessionContext;
@EJB
UserEJB userEJB;
@EJB
UserMgmtEJB userMgmtEJB;
@EJB
PermissionEJB permissionEJB;
// returns null if the entity doesn't exist.
public MediaFile findById(long id) {
MediaFile result = em.find(MediaFile.class, id);
em.detach(result);
return result;
}
@RolesAllowed(SecurityRoles.USER)
public MediaFile createNewMediaFile(String rootDir, String origFile) {
User user = userEJB.getCurrentUser();
return createNewMediaFile(rootDir, origFile, user);
}
@RolesAllowed(SecurityRoles.SYSTEM)
public MediaFile createNewMediaFile(String rootDir, String origFile, String uploadingUser) {
User user = userMgmtEJB.findUserByName(uploadingUser);
return createNewMediaFile(rootDir, origFile, user);
}
private MediaFile createNewMediaFile(String rootDir, String origFile, User user) {
MediaFile result = new MediaFile();
result.setRootDir(rootDir);
result.setOrigFile(origFile);
result.setCreationDate(new Date());
result.setUploadingUser(user);
result.setStatus(MediaFileImportStatus.NEW);
em.persist(result);
scheduleGenerateProxyVideosTask(result);
scheduleGenerateThumbnailTask(result, 0L);
em.flush(); // must flush before detatch
em.detach(result);
return result;
}
private List<MediaFile> detach(List<MediaFile> list) {
for (MediaFile mediaFile : list) {
em.detach(mediaFile);
}
return list;
}
public long countFiles(MediaFileImportStatus... statusValues) {
Query query = em.createNamedQuery("countMediaFileByStatusListAndUser")
.setParameter("user", userEJB.getCurrentUser())
.setParameter("statusList", Arrays.asList(statusValues));
return (long) query.getSingleResult();
}
public List<MediaFile> findFiles(MediaFileImportStatus... statusValues) {
Query query = em.createNamedQuery("findMediaFileByStatusListAndUser")
.setParameter("user", userEJB.getCurrentUser())
.setParameter("statusList", Arrays.asList(statusValues));
List<MediaFile> result = query.getResultList();
if (result == null) {
result = new ArrayList<>();
}
return detach(result);
}
@RolesAllowed(SecurityRoles.SYSTEM)
public MediaFile grabNextFileProcessorTask(Class... types) {
return grabNextFileProcessorTask(Arrays.asList(types));
}
@RolesAllowed(SecurityRoles.SYSTEM)
public MediaFile grabNextFileProcessorTask(Collection<Class> types) {
try {
Query query = em.createNamedQuery("findMediaFileWithPendingTasks")
.setParameter("classes", types);
query.setMaxResults(1);
List<MediaFile> result = query.getResultList();
if (result != null && result.size() > 0) {
MediaFile file = result.get(0);
FileProcessorTask nextTask = file.getPendingTasksQueue().get(0);
if ( nextTask.getStatus() != FileProcessorTaskStatus.PENDING ) {
throw new IllegalStateException("Query for pending tasks delivered task that is not pending.");
}
nextTask.setStatus(FileProcessorTaskStatus.IN_PROGRESS);
em.persist(nextTask);
em.flush(); // throws OptimisticLockException
em.detach(file);
return file;
}
return null;
}
catch ( OptimisticLockException e ) {
return null;
}
}
public List<MediaFile> findPublicFiles() {
Query query = em.createNamedQuery("findMediaFileByStatusAndAccess")
.setParameter("status", MediaFileImportStatus.READY)
.setParameter("access", Access.PUBLIC);
List<MediaFile> result = query.getResultList();
if (result == null) {
result = new ArrayList<>();
}
return detach(result);
}
// returns attached MediaFile
private MediaFile load(long id) throws NotFoundException {
MediaFile jpaEntity = em.find(MediaFile.class, id);
if (jpaEntity == null) {
throw new NotFoundException(MediaFile.class, id);
}
return jpaEntity;
}
public boolean hasPublicFiles() {
return findNumberOfPublicFiles() > 0;
}
public int findNumberOfPublicFiles() {
// TODO: This should be implemented without loading all files.
return findPublicFiles().size();
}
// TODO
public void updateAccessAndMetaData(MediaFile mediaFile, Access access, MediaFileUserProvidedMetaData metaData) throws NotFoundException {
if ( mediaFile.getStatus() != MediaFileImportStatus.FILEPROCESSOR_DONE && mediaFile.getStatus() != MediaFileImportStatus.READY ) {
throw new IllegalStateException("Cannot update meta data for file in status " + mediaFile.getStatus());
}
MediaFile file = load(mediaFile.getId());
file.setAccess(access);
file.setUserProvidedMetadata(metaData);
file.setStatus(MediaFileImportStatus.READY);
}
private void scheduleTask(MediaFile mediaFile, FileProcessorTask task) {
List<FileProcessorTask> pendingTasks = mediaFile.getPendingTasksQueue();
if ( pendingTasks == null ) {
pendingTasks = new LinkedList<>();
mediaFile.setPendingTasksQueue(pendingTasks);
}
task.setStatus(FileProcessorTaskStatus.PENDING);
task.setCreationDate(new Date());
pendingTasks.add(task);
task.setFile(mediaFile);
em.persist(task);
em.flush();
}
private void scheduleGenerateProxyVideosTask(MediaFile mediaFile) {
GenerateProxyVideosTask task = new GenerateProxyVideosTask();
scheduleTask(mediaFile, task);
}
@RolesAllowed(SecurityRoles.USER)
public void scheduleGenerateThumbnailsTask(long mediaFileId, Long thumbnailOffsetMs) throws NotFoundException {
MediaFile mediaFile = load(mediaFileId);
scheduleGenerateThumbnailTask(mediaFile, thumbnailOffsetMs);
}
private void scheduleGenerateThumbnailTask(MediaFile mediaFile, long thumbnailOffsetMs) {
GenerateThumbnailImagesTask task = new GenerateThumbnailImagesTask();
task.setThumbnailOffsetMs(thumbnailOffsetMs);
scheduleTask(mediaFile, task);
}
@RolesAllowed(SecurityRoles.USER)
public void scheduleDeleteTask(long fileId) throws NotFoundException {
MediaFile mediaFile = load(fileId);
DeleteTask deleteTask = new DeleteTask();
scheduleTask(mediaFile, deleteTask);
// Remove all other tasks that are not IN_PROGRESS
// Tasks IN_PROGRESS must first be continued, because there
// might be a script running producing files that need to
// be removed on deletion.
List<FileProcessorTask> tasksToRemove = new ArrayList<>();
for ( FileProcessorTask task : mediaFile.getPendingTasksQueue() ) {
if ( task != deleteTask && task.getStatus() == FileProcessorTaskStatus.PENDING ) {
tasksToRemove.add(task);
}
}
for ( FileProcessorTask task : tasksToRemove ) {
mediaFile.getPendingTasksQueue().remove(task);
em.remove(task);
}
em.flush();
}
private void updateImportStatus(MediaFile file) {
if ( file.getStatus() == MediaFileImportStatus.NEW ) {
if ( file.getProxyVideoData() != null && file.getThumbnailData() != null ) {
file.setStatus(MediaFileImportStatus.FILEPROCESSOR_DONE);
}
}
}
@RolesAllowed(SecurityRoles.SYSTEM)
public void handleTaskResult(long mediaFileId, FileProcessorTaskType type, ReturnStatus status, Map<String, String> data) throws NotFoundException, NoSuchTaskException {
if ( status == null || type == null ) {
throw new IllegalStateException();
}
MediaFile mediaFile = load(mediaFileId);
if ( mediaFile.getPendingTasksQueue().size() == 0 ) {
throw new NoSuchTaskException();
}
FileProcessorTask task = mediaFile.getPendingTasksQueue().get(0);
if ( task.getStatus() != FileProcessorTaskStatus.IN_PROGRESS ) {
throw new NoSuchTaskException();
}
switch ( type ) {
case GENERATE_PROXY_VIDEOS:
if ( ! ( task instanceof GenerateProxyVideosTask ) ) {
throw new NoSuchTaskException();
}
handleGenerateProxyVideoResult(mediaFile, status, data);
break;
case GENERATE_THUMBNAILS:
if ( ! ( task instanceof GenerateThumbnailImagesTask ) ) {
throw new NoSuchTaskException();
}
handleGenerateThumbnailsResult(mediaFile, status, data);
break;
case DELETE:
if ( ! ( task instanceof DeleteTask ) ) {
throw new NoSuchTaskException();
}
handleDeleteResult(mediaFile, status, data);
break;
default:
throw new IllegalArgumentException();
}
}
private void handleGenerateProxyVideoResult(MediaFile mediaFile, ReturnStatus status, Map<String, String> data) {
if ( status == ReturnStatus.ERROR ) {
if ( mediaFile.getStatus() == MediaFileImportStatus.NEW ) {
mediaFile.setStatus(MediaFileImportStatus.FILEPROCESSOR_FAILED);
}
} else {
MediaFileProxyVideoData proxyVideoData = new MediaFileProxyVideoData();
proxyVideoData.setLowResWebm(data.get(FileProcessorTaskDataKeys.LOW_RES_WEMB));
proxyVideoData.setLowResMp4(data.get(FileProcessorTaskDataKeys.LOW_RES_MP4));
mediaFile.setProxyVideoData(proxyVideoData);
updateImportStatus(mediaFile);
}
FileProcessorTask task = mediaFile.getPendingTasksQueue().get(0);
mediaFile.getPendingTasksQueue().remove(0);
em.remove(task);
em.flush();
}
private void handleGenerateThumbnailsResult(MediaFile mediaFile, ReturnStatus status, Map<String, String> data) {
if ( status == ReturnStatus.ERROR ) {
if ( mediaFile.getStatus() == MediaFileImportStatus.NEW ) {
mediaFile.setStatus(MediaFileImportStatus.FILEPROCESSOR_FAILED);
}
} else {
MediaFileThumbnailData thumbnailData = new MediaFileThumbnailData();
thumbnailData.setLargeImg(data.get(FileProcessorTaskDataKeys.LARGE_IMG));
thumbnailData.setMediumImg(data.get(FileProcessorTaskDataKeys.MEDIUM_IMG));
thumbnailData.setSmallImg(data.get(FileProcessorTaskDataKeys.SMALL_IMG));
thumbnailData.setThumbnailOffsetMs(Long.parseLong(data.get(FileProcessorTaskDataKeys.THUMBNAIL_OFFSET_MS)));
mediaFile.setThumbnailData(thumbnailData);
updateImportStatus(mediaFile);
}
FileProcessorTask task = mediaFile.getPendingTasksQueue().get(0);
mediaFile.getPendingTasksQueue().remove(0);
em.remove(task);
em.flush();
}
private void handleDeleteResult(MediaFile mediaFile, ReturnStatus status, Map<String, String> data) {
FileProcessorTask task = mediaFile.getPendingTasksQueue().get(0);
mediaFile.getPendingTasksQueue().remove(0);
em.remove(task);
if ( status == ReturnStatus.OK ) {
em.remove(mediaFile);
}
em.flush();
}
}