/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.repository.manager; import java.io.File; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.persistence.NoResultException; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.Group; import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; import org.olat.basesecurity.manager.GroupDAO; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.services.mark.MarkManager; import org.olat.core.commons.services.taskexecutor.manager.PersistentTaskDAO; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.id.Roles; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LearningResourceLoggingAction; import org.olat.core.logging.activity.OlatResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.CodeHelper; import org.olat.core.util.StringHelper; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.course.assessment.manager.AssessmentModeDAO; import org.olat.course.assessment.manager.UserCourseInformationsManager; import org.olat.course.certificate.CertificatesManager; import org.olat.ims.qti21.manager.AssessmentTestSessionDAO; import org.olat.modules.assessment.manager.AssessmentEntryDAO; import org.olat.modules.reminder.manager.ReminderDAO; import org.olat.repository.ErrorList; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryAllowToLeaveOptions; import org.olat.repository.RepositoryEntryAuthorView; import org.olat.repository.RepositoryEntryMyView; import org.olat.repository.RepositoryEntryRef; import org.olat.repository.RepositoryEntryRelationType; import org.olat.repository.RepositoryEntryStatus; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryModule; import org.olat.repository.RepositoryService; import org.olat.repository.handlers.RepositoryHandler; import org.olat.repository.handlers.RepositoryHandlerFactory; import org.olat.repository.model.RepositoryEntryLifecycle; import org.olat.repository.model.RepositoryEntryStatistics; import org.olat.repository.model.RepositoryEntryToGroupRelation; import org.olat.repository.model.SearchAuthorRepositoryEntryViewParams; import org.olat.repository.model.SearchMyRepositoryEntryViewParams; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; import org.olat.resource.accesscontrol.manager.ACReservationDAO; import org.olat.resource.references.ReferenceManager; import org.olat.search.service.document.RepositoryEntryDocument; import org.olat.search.service.indexer.LifeFullIndexer; import org.olat.util.logging.activity.LoggingResourceable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * * Initial date: 20.02.2014<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ @Service("repositoryService") public class RepositoryServiceImpl implements RepositoryService { private static final OLog log = Tracing.createLoggerFor(RepositoryServiceImpl.class); @Autowired private DB dbInstance; @Autowired private GroupDAO groupDao; @Autowired private CatalogManager catalogManager; @Autowired private BaseSecurity securityManager; @Autowired private ACReservationDAO reservationDao; @Autowired private ReferenceManager referenceManager; @Autowired private RepositoryEntryDAO repositoryEntryDAO; @Autowired private RepositoryEntryRelationDAO reToGroupDao; @Autowired private RepositoryEntryStatisticsDAO repositoryEntryStatisticsDao; @Autowired private RepositoryEntryMyCourseQueries myCourseViewQueries; @Autowired private RepositoryEntryAuthorQueries authorViewQueries; @Autowired private RepositoryHandlerFactory repositoryHandlerFactory; @Autowired private RepositoryModule repositoryModule; @Autowired private OLATResourceManager resourceManager; @Autowired private CertificatesManager certificatesManager; @Autowired private UserCourseInformationsManager userCourseInformationsManager; @Autowired private AssessmentModeDAO assessmentModeDao; @Autowired private AssessmentTestSessionDAO assessmentTestSessionDao; @Autowired private PersistentTaskDAO persistentTaskDao; @Autowired private ReminderDAO reminderDao; @Autowired private AssessmentEntryDAO assessmentEntryDao; @Autowired private LifeFullIndexer lifeIndexer; @Override public RepositoryEntry create(String initialAuthor, String resourceName, String displayname, String description, OLATResource resource) { return create(initialAuthor, null, resourceName, displayname, description, resource, 0); } @Override public RepositoryEntry create(Identity initialAuthor, String initialAuthorAlt, String resourceName, String displayname, String description, OLATResource resource, int access) { return create(initialAuthorAlt, initialAuthor, resourceName, displayname, description, resource, access); } private RepositoryEntry create(String initialAuthorName, Identity initialAuthor, String resourceName, String displayname, String description, OLATResource resource, int access) { Date now = new Date(); RepositoryEntry re = new RepositoryEntry(); if(StringHelper.containsNonWhitespace(initialAuthorName)) { re.setInitialAuthor(initialAuthorName); } else if(initialAuthor != null) { re.setInitialAuthor(initialAuthor.getName()); } else { re.setInitialAuthor("-"); } re.setCreationDate(now); re.setLastModified(now); re.setAccess(access); re.setCanDownload(false); re.setCanCopy(false); re.setCanReference(false); re.setCanLaunch(true); re.setDisplayname(displayname); re.setResourcename(StringHelper.containsNonWhitespace(resourceName) ? resourceName : "-"); re.setDescription(description == null ? "" : description); re.setAllowToLeaveOption(repositoryModule.getAllowToLeaveDefaultOption()); if(resource == null) { OLATResourceable ores = OresHelper.createOLATResourceableInstance("RepositoryEntry", CodeHelper.getForeverUniqueID()); resource = resourceManager.createAndPersistOLATResourceInstance(ores); } else if(resource != null && resource.getKey() == null) { dbInstance.getCurrentEntityManager().persist(resource); } re.setOlatResource(resource); RepositoryEntryStatistics statistics = new RepositoryEntryStatistics(); statistics.setLastUsage(now); statistics.setCreationDate(now); statistics.setLastModified(now); statistics.setDownloadCounter(0l); statistics.setLaunchCounter(0l); statistics.setNumOfRatings(0l); statistics.setNumOfComments(0l); dbInstance.getCurrentEntityManager().persist(statistics); re.setStatistics(statistics); Group group = groupDao.createGroup(); RepositoryEntryToGroupRelation rel = new RepositoryEntryToGroupRelation(); rel.setCreationDate(new Date()); rel.setDefaultGroup(true); rel.setGroup(group); rel.setEntry(re); Set<RepositoryEntryToGroupRelation> rels = new HashSet<>(2); rels.add(rel); re.setGroups(rels); if(initialAuthor != null) { groupDao.addMembershipTwoWay(group, initialAuthor, GroupRoles.owner.name()); } dbInstance.getCurrentEntityManager().persist(re); return re; } @Override public RepositoryEntry copy(RepositoryEntry sourceEntry, Identity author, String displayname) { OLATResource sourceResource = sourceEntry.getOlatResource(); OLATResource copyResource = resourceManager.createOLATResourceInstance(sourceResource.getResourceableTypeName()); RepositoryEntry copyEntry = create(author, null, sourceEntry.getResourcename(), displayname, sourceEntry.getDescription(), copyResource, RepositoryEntry.ACC_OWNERS); //copy all fields copyEntry.setAuthors(sourceEntry.getAuthors()); copyEntry.setCredits(sourceEntry.getCredits()); copyEntry.setExpenditureOfWork(sourceEntry.getExpenditureOfWork()); copyEntry.setMainLanguage(sourceEntry.getMainLanguage()); copyEntry.setObjectives(sourceEntry.getObjectives()); copyEntry.setRequirements(sourceEntry.getRequirements()); copyEntry = dbInstance.getCurrentEntityManager().merge(copyEntry); RepositoryHandler handler = RepositoryHandlerFactory.getInstance().getRepositoryHandler(sourceEntry); copyEntry = handler.copy(author, sourceEntry, copyEntry); //copy the image RepositoryManager.getInstance().copyImage(sourceEntry, copyEntry); //copy media container VFSContainer sourceMediaContainer = handler.getMediaContainer(sourceEntry); if(sourceMediaContainer != null) { VFSContainer targetMediaContainer = handler.getMediaContainer(copyEntry); VFSManager.copyContent(sourceMediaContainer, targetMediaContainer); } ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_CREATE, getClass(), LoggingResourceable.wrap(copyEntry, OlatResourceableType.genRepoEntry)); lifeIndexer.indexDocument(RepositoryEntryDocument.TYPE, copyEntry.getKey()); return copyEntry; } @Override public RepositoryEntry update(RepositoryEntry re) { re.setLastModified(new Date()); RepositoryEntry mergedRe = dbInstance.getCurrentEntityManager().merge(re); dbInstance.commit(); lifeIndexer.indexDocument(RepositoryEntryDocument.TYPE, mergedRe.getKey()); return mergedRe; } @Override public RepositoryEntry loadByKey(Long key) { return repositoryEntryDAO.loadByKey(key); } @Override public RepositoryEntry loadByResourceKey(Long resourceKey) { return repositoryEntryDAO.loadByResourceKey(resourceKey); } @Override public List<RepositoryEntry> loadByResourceKeys(Collection<Long> resourceKeys) { return repositoryEntryDAO.loadByResourceKeys(resourceKeys); } @Override public OLATResource loadRepositoryEntryResource(Long repositoryEntryKey) { return repositoryEntryDAO.loadRepositoryEntryResource(repositoryEntryKey); } @Override public OLATResource loadRepositoryEntryResourceBySoftKey(String softkey) { return repositoryEntryDAO.loadRepositoryEntryResourceBySoftKey(softkey); } @Override public VFSLeaf getIntroductionImage(RepositoryEntry re) { VFSContainer repositoryHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRepositoryHome())); String imageName = re.getResourceableId() + ".jpg"; VFSItem image = repositoryHome.resolve(imageName); if(image instanceof VFSLeaf) { return (VFSLeaf)image; } imageName = re.getResourceableId() + ".png"; image = repositoryHome.resolve(imageName); if(image instanceof VFSLeaf) { return (VFSLeaf)image; } return null; } @Override public VFSLeaf getIntroductionMovie(RepositoryEntry re) { RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(re); VFSContainer mediaContainer = handler.getMediaContainer(re); if(mediaContainer != null) { List<VFSItem> items = mediaContainer.getItems(); for(VFSItem item:items) { if(item instanceof VFSLeaf && item.getName().startsWith(re.getKey().toString()) && (item.getName().endsWith(".mp4") || item.getName().endsWith(".m4v") || item.getName().endsWith(".flv")) ) { return (VFSLeaf)item; } } } return null; } @Override public RepositoryEntry deleteSoftly(RepositoryEntry re, Identity deletedBy, boolean owners) { RepositoryEntry reloadedRe = repositoryEntryDAO.loadForUpdate(re); reloadedRe.setAccess(RepositoryEntry.DELETED); if(reloadedRe.getDeletionDate() == null) { // don't write the name of an admin which make a restore -> delete operation reloadedRe.setDeletedBy(deletedBy); reloadedRe.setDeletionDate(new Date()); } reloadedRe = dbInstance.getCurrentEntityManager().merge(reloadedRe); dbInstance.commit(); //remove from catalog catalogManager.resourceableDeleted(reloadedRe); //remove participant and coach if(owners) { removeMembers(reloadedRe, GroupRoles.owner.name(), GroupRoles.coach.name(), GroupRoles.participant.name(), GroupRoles.waiting.name()); } else { removeMembers(reloadedRe, GroupRoles.coach.name(), GroupRoles.participant.name(), GroupRoles.waiting.name()); } //remove relation to business groups List<RepositoryEntryToGroupRelation> relations = reToGroupDao.getRelations(reloadedRe); for(RepositoryEntryToGroupRelation relation:relations) { if(!relation.isDefaultGroup()) { reToGroupDao.removeRelation(relation); } } dbInstance.commit(); return reloadedRe; } @Override public RepositoryEntry restoreRepositoryEntry(RepositoryEntry entry) { RepositoryEntry reloadedRe = repositoryEntryDAO.loadForUpdate(entry); reloadedRe.setAccess(RepositoryEntry.ACC_OWNERS); reloadedRe.setStatusCode(RepositoryEntryStatus.REPOSITORY_STATUS_CLOSED); reloadedRe = dbInstance.getCurrentEntityManager().merge(reloadedRe); dbInstance.commit(); return reloadedRe; } @Override public ErrorList deletePermanently(RepositoryEntry entry, Identity identity, Roles roles, Locale locale) { ErrorList errors = new ErrorList(); boolean debug = log.isDebug(); // invoke handler delete callback if(debug) log.debug("deleteRepositoryEntry start entry=" + entry); entry = (RepositoryEntry) dbInstance.loadObject(entry,true); if(debug) log.debug("deleteRepositoryEntry after load entry=" + entry); RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(entry); OLATResource resource = entry.getOlatResource(); //delete old context if (!handler.readyToDelete(entry, identity, roles, locale, errors)) { return errors; } userCourseInformationsManager.deleteUserCourseInformations(entry); certificatesManager.deleteRepositoryEntry(entry); // delete all bookmarks referencing deleted entry CoreSpringFactory.getImpl(MarkManager.class).deleteMarks(entry); // delete all catalog entries referencing deleted entry catalogManager.resourceableDeleted(entry); // delete assessment modes assessmentModeDao.delete(entry); // delete reminders reminderDao.delete(entry); //delete all policies securityManager.deletePolicies(resource); //delete reservations reservationDao.deleteReservations(resource); //delete references referenceManager.deleteAllReferencesOf(resource); //delete all pending tasks persistentTaskDao.delete(resource); dbInstance.commit(); // inform handler to do any cleanup work... handler must delete the // referenced resourceable a swell. handler.cleanupOnDelete(entry, resource); dbInstance.commit(); //delete all test sessions assessmentTestSessionDao.deleteAllUserTestSessionsByCourse(entry); //nullify the reference assessmentEntryDao.removeEntryForReferenceEntry(entry); assessmentEntryDao.deleteEntryForRepositoryEntry(entry); dbInstance.commit(); if(debug) log.debug("deleteRepositoryEntry after reload entry=" + entry); deleteRepositoryEntryAndBaseGroups(entry); if(debug) log.debug("deleteRepositoryEntry Done"); return errors; } /** * * @param entry */ @Override public void deleteRepositoryEntryAndBaseGroups(RepositoryEntry entry) { RepositoryEntry reloadedEntry = dbInstance.getCurrentEntityManager() .getReference(RepositoryEntry.class, entry.getKey()); Long resourceKey = reloadedEntry.getOlatResource().getKey(); Group defaultGroup = null; try { defaultGroup = reToGroupDao.getDefaultGroup(reloadedEntry); groupDao.removeMemberships(defaultGroup); } catch (NoResultException e) { log.error("", e); } reToGroupDao.removeRelations(reloadedEntry); dbInstance.commit(); dbInstance.getCurrentEntityManager().remove(reloadedEntry); if(defaultGroup != null) { groupDao.removeGroup(defaultGroup); } dbInstance.commit(); OLATResource reloadedResource = resourceManager.findResourceById(resourceKey); if(reloadedResource != null) { dbInstance.getCurrentEntityManager().remove(reloadedResource); } dbInstance.commit(); } @Override public RepositoryEntry closeRepositoryEntry(RepositoryEntry entry) { RepositoryEntry reloadedEntry = repositoryEntryDAO.loadForUpdate(entry); reloadedEntry.setStatusCode(RepositoryEntryStatus.REPOSITORY_STATUS_CLOSED); reloadedEntry = dbInstance.getCurrentEntityManager().merge(reloadedEntry); dbInstance.commit(); return reloadedEntry; } @Override public RepositoryEntry uncloseRepositoryEntry(RepositoryEntry entry) { RepositoryEntry reloadedEntry = repositoryEntryDAO.loadForUpdate(entry); reloadedEntry.setStatusCode(RepositoryEntryStatus.REPOSITORY_STATUS_OPEN); reloadedEntry = dbInstance.getCurrentEntityManager().merge(reloadedEntry); dbInstance.commit(); return reloadedEntry; } @Override public RepositoryEntry unpublishRepositoryEntry(RepositoryEntry entry) { RepositoryEntry reloadedEntry = repositoryEntryDAO.loadForUpdate(entry); reloadedEntry.setStatusCode(RepositoryEntryStatus.REPOSITORY_STATUS_UNPUBLISHED); reloadedEntry = dbInstance.getCurrentEntityManager().merge(reloadedEntry); dbInstance.commit(); // remove catalog entries catalogManager.resourceableDeleted(reloadedEntry); // remove users and participants //remove participant and coach removeMembers(reloadedEntry, GroupRoles.coach.name(), GroupRoles.participant.name(), GroupRoles.waiting.name()); //remove relation to business groups List<RepositoryEntryToGroupRelation> relations = reToGroupDao.getRelations(reloadedEntry); for(RepositoryEntryToGroupRelation relation:relations) { if(!relation.isDefaultGroup()) { reToGroupDao.removeRelation(relation); } } return reloadedEntry; } @Override public void incrementLaunchCounter(RepositoryEntry re) { repositoryEntryStatisticsDao.incrementLaunchCounter(re); } @Override public void incrementDownloadCounter(RepositoryEntry re) { repositoryEntryStatisticsDao.incrementDownloadCounter(re); } @Override public void setLastUsageNowFor(RepositoryEntry re) { repositoryEntryStatisticsDao.setLastUsageNowFor(re); } @Override public Group getDefaultGroup(RepositoryEntryRef ref) { return reToGroupDao.getDefaultGroup(ref); } /** * Get the role in the specified resource, business group are included in * the query. * */ @Override public List<String> getRoles(Identity identity, RepositoryEntryRef re) { return reToGroupDao.getRoles(identity, re); } /** * Has specific role in the specified resource WITHOUT business groups included in * the query. */ @Override public boolean hasRole(Identity identity, RepositoryEntryRef re, String... roles) { return reToGroupDao.hasRole(identity, re, roles); } @Override public boolean hasRole(Identity identity, boolean followBusinessGroups, String... roles) { return reToGroupDao.hasRole(identity, followBusinessGroups, roles); } @Override public boolean isParticipantAllowedToLeave(RepositoryEntry re) { boolean allowed = false; RepositoryEntryAllowToLeaveOptions setting = re.getAllowToLeaveOption(); if(setting == RepositoryEntryAllowToLeaveOptions.atAnyTime) { allowed = true; } else if(setting == RepositoryEntryAllowToLeaveOptions.afterEndDate) { RepositoryEntryLifecycle lifecycle = re.getLifecycle(); if(lifecycle == null || lifecycle.getValidTo() == null) { allowed = false; } else { Calendar cal = Calendar.getInstance(); cal.set(Calendar.HOUR, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); Date now = cal.getTime(); if(now.compareTo(lifecycle.getValidTo()) >= 0) { allowed = true; } else { allowed = false; } } } else { allowed = false; } return allowed; } @Override public boolean isMember(IdentityRef identity, RepositoryEntryRef entry) { return reToGroupDao.isMember(identity, entry); } @Override public void filterMembership(IdentityRef identity, List<Long> entries) { reToGroupDao.filterMembership(identity, entries); } @Override public int countMembers(RepositoryEntryRef re, String... roles) { return reToGroupDao.countMembers(re, roles); } @Override public int countMembers(List<? extends RepositoryEntryRef> res, Identity excludeMe) { return reToGroupDao.countMembers(res, excludeMe); } @Override public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) { return reToGroupDao.getEnrollmentDate(re, identity, roles); } @Override public Map<Long, Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) { return reToGroupDao.getEnrollmentDates(re, roles); } @Override public List<Long> getAuthors(RepositoryEntryRef re) { return reToGroupDao.getAuthorKeys(re); } @Override public List<Identity> getMembers(RepositoryEntryRef re, String... roles) { return reToGroupDao.getMembers(re, RepositoryEntryRelationType.defaultGroup, roles); } @Override public List<Identity> getMembers(List<? extends RepositoryEntryRef> res, RepositoryEntryRelationType relationType, String... roles) { return reToGroupDao.getMembers(res, relationType, roles); } @Override public List<Identity> getIdentitiesWithRole(String role) { return reToGroupDao.getIdentitiesWithRole(role); } @Override public void addRole(Identity identity, RepositoryEntry re, String role) { reToGroupDao.addRole(identity, re, role); } @Override public void removeRole(Identity identity, RepositoryEntry re, String role) { reToGroupDao.removeRole(identity, re, role); } @Override public void removeMembers(RepositoryEntry re, String... roles) { if(roles == null || roles.length == 0) return; for(String role:roles) { if(role != null) { reToGroupDao.removeRole(re, role); } } } @Override public List<RepositoryEntry> searchByIdAndRefs(String idAndRefs) { return repositoryEntryDAO.searchByIdAndRefs(idAndRefs); } @Override public int countMyView(SearchMyRepositoryEntryViewParams params) { return myCourseViewQueries.countViews(params); } @Override public List<RepositoryEntryMyView> searchMyView(SearchMyRepositoryEntryViewParams params, int firstResult, int maxResults) { return myCourseViewQueries.searchViews(params, firstResult, maxResults); } @Override public int countAuthorView(SearchAuthorRepositoryEntryViewParams params) { return authorViewQueries.countViews(params); } @Override public List<RepositoryEntryAuthorView> searchAuthorView(SearchAuthorRepositoryEntryViewParams params, int firstResult, int maxResults) { return authorViewQueries.searchViews(params, firstResult, maxResults); } }