/** * <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.course.certificate.manager; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.UUID; import javax.annotation.Resource; import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.ObjectMessage; import javax.jms.Queue; import javax.jms.QueueConnection; import javax.jms.QueueSender; import javax.jms.QueueSession; import javax.jms.Session; import javax.persistence.TypedQuery; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; import org.olat.admin.user.imp.TransientIdentity; import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.modules.bc.FolderModule; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.PublisherData; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.id.UserConstants; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.mail.ContactList; import org.olat.core.util.mail.MailBundle; import org.olat.core.util.mail.MailManager; import org.olat.core.util.mail.MailerResult; import org.olat.core.util.vfs.FileStorage; 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.CorruptedCourseException; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.certificate.Certificate; import org.olat.course.certificate.CertificateEvent; import org.olat.course.certificate.CertificateLight; import org.olat.course.certificate.CertificateStatus; import org.olat.course.certificate.CertificateTemplate; import org.olat.course.certificate.CertificatesManager; import org.olat.course.certificate.CertificatesModule; import org.olat.course.certificate.EmailStatus; import org.olat.course.certificate.RecertificationTimeUnit; import org.olat.course.certificate.model.CertificateImpl; import org.olat.course.certificate.model.CertificateInfos; import org.olat.course.certificate.model.CertificateStandalone; import org.olat.course.certificate.model.CertificateTemplateImpl; import org.olat.course.certificate.model.JmsCertificateWork; import org.olat.course.certificate.ui.CertificateController; import org.olat.course.config.CourseConfig; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; import org.olat.group.manager.BusinessGroupRelationDAO; import org.olat.group.model.SearchBusinessGroupParams; import org.olat.modules.vitero.model.GroupRole; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.repository.model.RepositoryEntrySecurity; import org.olat.resource.OLATResource; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.DatePropertyHandler; import org.olat.user.propertyhandlers.UserPropertyHandler; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import uk.ac.reload.diva.util.ZipUtils; /** * * Initial date: 20.10.2014<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ @Service("certificatesManager") public class CertificatesManagerImpl implements CertificatesManager, MessageListener, InitializingBean, DisposableBean { private static final OLog log = Tracing.createLoggerFor(CertificatesManagerImpl.class); private VelocityEngine velocityEngine; @Autowired private DB dbInstance; @Autowired private I18nManager i18nManager; @Autowired private MailManager mailManager; @Autowired private UserManager userManager; @Autowired private BaseSecurity securityManager; @Autowired private RepositoryService repositoryService; @Autowired private RepositoryManager repositoryManager; @Autowired private BusinessGroupService businessGroupService; @Autowired private BusinessGroupRelationDAO businessGroupRelationDao; @Autowired private CoordinatorManager coordinatorManager; @Autowired private NotificationsManager notificationsManager; @Autowired private FolderModule folderModule; @Autowired private CertificatesModule certificatesModule; @Resource(name="certificateQueue") private Queue jmsQueue; private Session certificateSession; private MessageConsumer consumer; @Resource(name="certificateConnectionFactory") private ConnectionFactory connectionFactory; private QueueConnection connection; private Boolean phantomAvailable; private FileStorage usersStorage; private FileStorage templatesStorage; @Override public void afterPropertiesSet() { //create the folders getCertificateTemplatesRoot(); templatesStorage = new FileStorage(getCertificateTemplatesRootContainer()); getCertificateRoot(); usersStorage = new FileStorage(getCertificateRootContainer()); Properties p = null; try { velocityEngine = new VelocityEngine(); p = new Properties(); p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"); p.setProperty("runtime.log.logsystem.log4j.category", "syslog"); velocityEngine.init(p); } catch (Exception e) { throw new RuntimeException("config error " + p.toString()); } //deploy script try(InputStream inRasteriez = CertificatesManager.class.getResourceAsStream("rasterize.js")) { Path rasterizePath = getRasterizePath(); Files.copy(inRasteriez, rasterizePath, StandardCopyOption.REPLACE_EXISTING); } catch(Exception e) { log.error("Can not read rasterize.js library for PhantomJS PDF generation", e); } try(InputStream inQRCodeLib = CertificatesManager.class.getResourceAsStream("qrcode.min.js")) { Path qrCodeLibPath = getQRCodeLibPath(); Files.copy(inQRCodeLib, qrCodeLibPath, StandardCopyOption.REPLACE_EXISTING); } catch(Exception e) { log.error("Can not read qrcode.min.js for QR Code PDF generation", e); } //start the queues try { startQueue(); } catch (JMSException e) { log.error("", e); } } private void startQueue() throws JMSException { connection = (QueueConnection)connectionFactory.createConnection(); connection.start(); log.info("springInit: JMS connection started with connectionFactory=" + connectionFactory); //listen to the queue only if indexing node certificateSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); consumer = certificateSession.createConsumer(jmsQueue); consumer.setMessageListener(this); } @Override public void destroy() throws Exception { closeJms(); } private void closeJms() { if(consumer != null) { try { consumer.close(); } catch (JMSException e) { log.error("", e); } } if(connection != null) { try { certificateSession.close(); connection.close(); } catch (JMSException e) { log.error("", e); } } } private Queue getJmsQueue() { return jmsQueue; } @Override public boolean isHTMLTemplateAllowed() { if(phantomAvailable == null) { phantomAvailable = CertificatePhantomWorker.checkPhantomJSAvailabilty(); } return phantomAvailable.booleanValue(); } @Override public SubscriptionContext getSubscriptionContext(ICourse course) { CourseNode cn = course.getRunStructure().getRootNode(); CourseEnvironment ce = course.getCourseEnvironment(); SubscriptionContext ctxt = new SubscriptionContext(ORES_CERTIFICATE, ce.getCourseResourceableId(), cn.getIdent()); return ctxt; } @Override public PublisherData getPublisherData(ICourse course, String businessPath) { String data = String.valueOf(course.getCourseEnvironment().getCourseResourceableId()); PublisherData pData = new PublisherData(ORES_CERTIFICATE, data, businessPath); return pData; } @Override public void markPublisherNews(Identity ident, ICourse course) { SubscriptionContext subsContext = getSubscriptionContext(course); if (subsContext != null) { notificationsManager.markPublisherNews(subsContext, ident, true); } } public void markPublisherNews(Identity ident, OLATResource courseResource) { ICourse course = CourseFactory.loadCourse(courseResource); SubscriptionContext subsContext = getSubscriptionContext(course); if (subsContext != null) { notificationsManager.markPublisherNews(subsContext, ident, true); } } @Override public int deleteRepositoryEntry(RepositoryEntry re) { StringBuilder sb = new StringBuilder(); sb.append("update certificate set olatResource = null where olatResource.key=:resourceKey"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString()) .setParameter("resourceKey", re.getOlatResource().getKey()) .executeUpdate(); } @Override public List<OLATResource> getResourceWithCertificates() { StringBuilder sb = new StringBuilder(); sb.append("select distinct resource from certificate cer") .append(" inner join cer.olatResource resource"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), OLATResource.class) .getResultList(); } @Override public VFSLeaf getCertificateLeaf(Certificate certificate) { VFSContainer cerContainer = getCertificateRootContainer(); VFSItem cerItem = null; if(StringHelper.containsNonWhitespace(certificate.getPath())) { cerItem = cerContainer.resolve(certificate.getPath()); } return cerItem instanceof VFSLeaf ? (VFSLeaf)cerItem : null; } private File getCertificateFile(Certificate certificate) { File file = getCertificateRoot(); if(StringHelper.containsNonWhitespace(certificate.getPath())) { return new File(file, certificate.getPath()); } return null; } @Override public CertificateImpl getCertificateById(Long key) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificate cer") .append(" inner join fetch cer.identity ident") .append(" inner join fetch ident.user identUser") .append(" where cer.key=:certificateKey"); List<CertificateImpl> certificates = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), CertificateImpl.class) .setParameter("certificateKey", key) .getResultList(); return certificates.isEmpty() ? null : certificates.get(0); } @Override public CertificateLight getCertificateLightById(Long key) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificatelight cer") .append(" where cer.key=:certificateKey"); List<CertificateLight> certificates = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), CertificateLight.class) .setParameter("certificateKey", key) .getResultList(); return certificates.isEmpty() ? null : certificates.get(0); } @Override public Certificate getCertificateByUuid(String uuid) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificate cer") .append(" where cer.uuid=:uuid"); List<Certificate> certificates = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Certificate.class) .setParameter("uuid", uuid) .getResultList(); return certificates.isEmpty() ? null : certificates.get(0); } @Override public boolean hasCertificate(IdentityRef identity, Long resourceKey) { StringBuilder sb = new StringBuilder(); sb.append("select cer.key from certificate cer") .append(" where (cer.olatResource.key=:resourceKey or cer.archivedResourceKey=:resourceKey)") .append(" and cer.identity.key=:identityKey"); List<Number> certififcates = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Number.class) .setParameter("resourceKey", resourceKey) .setParameter("identityKey", identity.getKey()) .setFirstResult(0) .setMaxResults(1) .getResultList(); return certififcates != null && certififcates.size() > 0; } @Override public List<CertificateLight> getLastCertificates(IdentityRef identity) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificatelight cer") .append(" where cer.identityKey=:identityKey and cer.last=true"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), CertificateLight.class) .setParameter("identityKey", identity.getKey()) .getResultList(); } @Override public Certificate getLastCertificate(IdentityRef identity, Long resourceKey) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificate cer") .append(" where (cer.olatResource.key=:resourceKey or cer.archivedResourceKey=:resourceKey)") .append(" and cer.identity.key=:identityKey and cer.last=true order by cer.creationDate"); List<Certificate> certififcates = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Certificate.class) .setParameter("resourceKey", resourceKey) .setParameter("identityKey", identity.getKey()) .setMaxResults(1) .getResultList(); return certififcates.isEmpty() ? null : certififcates.get(0); } @Override public List<Certificate> getCertificatesForNotifications(Identity identity, RepositoryEntry entry, Date lastNews) { Roles roles = securityManager.getRoles(identity); RepositoryEntrySecurity security = repositoryManager.isAllowed(identity, roles, entry); if(!security.isEntryAdmin() && !security.isCourseCoach() && !security.isGroupCoach() && !security.isCourseParticipant() && !security.isGroupParticipant()) { return Collections.emptyList(); } StringBuilder sb = new StringBuilder(); sb.append("select cer from certificate cer") .append(" inner join fetch cer.identity ident") .append(" where cer.olatResource.key=:resourceKey and cer.last=true "); //must be some kind of restrictions boolean securityCheck = false; List<Long> baseGroupKeys = null; if(!security.isEntryAdmin()) { sb.append(" and ("); boolean or = false; if(security.isCourseCoach()) { or = or(sb, or); sb.append(" exists (select membership.identity.key from repoentrytogroup as rel, bgroup as reBaseGroup, bgroupmember membership ") .append(" where ident.key=membership.identity.key and rel.entry.key=:repoKey and rel.group=reBaseGroup and membership.group=reBaseGroup and membership.role='").append(GroupRole.participant).append("'") .append(" )"); securityCheck = true; } if(security.isGroupCoach()) { SearchBusinessGroupParams params = new SearchBusinessGroupParams(identity, true, false); List<BusinessGroup> groups = businessGroupService.findBusinessGroups(params, entry, 0, -1); if(groups.size() > 0) { or = or(sb, or); sb.append(" exists (select membership.identity.key from bgroupmember membership ") .append(" where ident.key=membership.identity.key and membership.group.key in (:groups) and membership.role='").append(GroupRole.participant).append("'") .append(" )"); baseGroupKeys = new ArrayList<>(groups.size()); for(BusinessGroup group:groups) { baseGroupKeys.add(group.getBaseGroup().getKey()); } securityCheck = true; } } if(security.isCourseParticipant() || security.isGroupParticipant()) { or = or(sb, or); sb.append(" ident.key=:identityKey"); securityCheck = true; } sb.append(")"); } else { securityCheck = true; } if(!securityCheck) { return Collections.emptyList(); } sb.append(" order by cer.creationDate"); TypedQuery<Certificate> certificates = dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Certificate.class) .setParameter("resourceKey", entry.getOlatResource().getKey()); if(!security.isEntryAdmin()) { if(security.isCourseCoach()) { certificates.setParameter("repoKey", entry.getKey()); } if(security.isCourseParticipant() || security.isGroupParticipant()) { certificates.setParameter("identityKey", identity.getKey()); } } if(baseGroupKeys != null && !baseGroupKeys.isEmpty()) { certificates.setParameter("groups", baseGroupKeys); } return certificates.getResultList(); } private final boolean or(StringBuilder sb, boolean or) { if(or) sb.append(" or "); else sb.append(" "); return true; } @Override public List<Certificate> getCertificates(OLATResource resource) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificate cer") .append(" where cer.olatResource.key=:resourceKey"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Certificate.class) .setParameter("resourceKey", resource.getKey()) .getResultList(); } @Override public List<Certificate> getCertificates(IdentityRef identity, OLATResource resource) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificate cer") .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey order by cer.creationDate desc"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), Certificate.class) .setParameter("resourceKey", resource.getKey()) .setParameter("identityKey", identity.getKey()) .getResultList(); } @Override public List<CertificateLight> getLastCertificates(OLATResource resource) { StringBuilder sb = new StringBuilder(); sb.append("select cer from certificatelight cer") .append(" where cer.olatResourceKey=:resourceKey and cer.last=true"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), CertificateLight.class) .setParameter("resourceKey", resource.getKey()) .getResultList(); } @Override public List<CertificateLight> getLastCertificates(BusinessGroup businessGroup) { List<BusinessGroup> groups = Collections.singletonList(businessGroup); List<RepositoryEntry> entries = businessGroupRelationDao.findRepositoryEntries(groups, 0, -1); List<Long> resourceKeys = new ArrayList<>(entries.size()); for(RepositoryEntry entry:entries) { resourceKeys.add(entry.getOlatResource().getKey()); } StringBuilder sb = new StringBuilder(); sb.append("select cer from certificatelight cer") .append(" where cer.olatResourceKey in (:resourceKeys) and cer.last=true"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), CertificateLight.class) .setParameter("resourceKeys", resourceKeys) .getResultList(); } @Override public boolean isCertificationAllowed(Identity identity, RepositoryEntry entry) { boolean allowed = false; try { ICourse course = CourseFactory.loadCourse(entry); CourseConfig config = course.getCourseEnvironment().getCourseConfig(); if(config.isRecertificationEnabled()) { Certificate certificate = getLastCertificate(identity, entry.getOlatResource().getKey()); if(certificate == null) { allowed = true; } else { Calendar cal = Calendar.getInstance(); Date now = cal.getTime(); Date nextCertificationDate = getDateNextRecertification(certificate, config); allowed = (nextCertificationDate != null ? nextCertificationDate.before(now) : false); } } else { allowed = !hasCertificate(identity, entry.getOlatResource().getKey()); } } catch (CorruptedCourseException e) { log.error("", e); } return allowed; } @Override public Date getDateNextRecertification(Certificate certificate, RepositoryEntry entry) { ICourse course = CourseFactory.loadCourse(entry); CourseConfig config = course.getCourseEnvironment().getCourseConfig(); return getDateNextRecertification(certificate, config); } private Date getDateNextRecertification(Certificate certificate, CourseConfig config) { if(config.isRecertificationEnabled() && certificate != null) { int time = config.getRecertificationTimelapse(); RecertificationTimeUnit timeUnit = config.getRecertificationTimelapseUnit(); Date date = certificate.getCreationDate(); Calendar cal = Calendar.getInstance(); cal.setTime(date); switch(timeUnit) { case day: cal.add(Calendar.DATE, time); break; case week: cal.add(Calendar.DATE, time * 7); break; case month: cal.add(Calendar.MONTH, time); break; case year: cal.add(Calendar.YEAR, time); break; } Date nextCertification = cal.getTime(); return nextCertification; } else { return null; } } @Override public void deleteCertificate(Certificate certificate) { File certificateFile = getCertificateFile(certificate); if(certificateFile != null && certificateFile.exists()) { try { FileUtils.deleteDirsAndFiles(certificateFile.getParentFile().toPath()); } catch (IOException e) { log.error("", e); } } CertificateImpl relaodedCertificate = dbInstance.getCurrentEntityManager() .getReference(CertificateImpl.class, certificate.getKey()); dbInstance.getCurrentEntityManager().remove(relaodedCertificate); //reorder the last flag List<Certificate> certificates = getCertificates(relaodedCertificate.getIdentity(), relaodedCertificate.getOlatResource()); certificates.remove(relaodedCertificate); if(certificates.size() > 0) { boolean hasLast = false; for(Certificate cer:certificates) { if(((CertificateImpl)cer).isLast()) { hasLast = true; } } if(!hasLast) { CertificateImpl newLastCertificate = (CertificateImpl)certificates.get(0); newLastCertificate.setLast(true); dbInstance.getCurrentEntityManager().merge(newLastCertificate); } } } @Override public Certificate uploadCertificate(Identity identity, Date creationDate, OLATResource resource, File certificateFile) { CertificateImpl certificate = new CertificateImpl(); certificate.setOlatResource(resource); certificate.setArchivedResourceKey(resource.getKey()); if(creationDate != null) { certificate.setCreationDate(creationDate); } RepositoryEntry entry = repositoryService.loadByResourceKey(resource.getKey()); if(entry != null) { certificate.setCourseTitle(entry.getDisplayname()); } certificate.setLastModified(certificate.getCreationDate()); certificate.setIdentity(identity); certificate.setUuid(UUID.randomUUID().toString()); certificate.setLast(true); certificate.setStatus(CertificateStatus.ok); String dir = usersStorage.generateDir(); try (InputStream in = Files.newInputStream(certificateFile.toPath())) { File dirFile = new File(getCertificateRoot(), dir); dirFile.mkdirs(); File storedCertificateFile = new File(dirFile, "Certificate.pdf"); Files.copy(in, storedCertificateFile.toPath(), StandardCopyOption.REPLACE_EXISTING); certificate.setPath(dir + storedCertificateFile.getName()); Date dateFirstCertification = getDateFirstCertification(identity, resource.getKey()); if (dateFirstCertification != null) { removeLastFlag(identity, resource.getKey()); } dbInstance.getCurrentEntityManager().persist(certificate); } catch (Exception e) { log.error("", e); } return certificate; } @Override public Certificate uploadStandaloneCertificate(Identity identity, Date creationDate, String courseTitle, Long resourceKey, File certificateFile) { CertificateStandalone certificate = new CertificateStandalone(); certificate.setArchivedResourceKey(resourceKey); if(creationDate != null) { certificate.setCreationDate(creationDate); } certificate.setLastModified(certificate.getCreationDate()); certificate.setIdentity(identity); certificate.setUuid(UUID.randomUUID().toString()); certificate.setLast(true); certificate.setCourseTitle(courseTitle); certificate.setStatus(CertificateStatus.ok); String dir = usersStorage.generateDir(); try (InputStream in = Files.newInputStream(certificateFile.toPath())) { File dirFile = new File(getCertificateRoot(), dir); dirFile.mkdirs(); File storedCertificateFile = new File(dirFile, "Certificate.pdf"); Files.copy(in, storedCertificateFile.toPath(), StandardCopyOption.REPLACE_EXISTING); certificate.setPath(dir + storedCertificateFile.getName()); Date dateFirstCertification = getDateFirstCertification(identity, resourceKey); if (dateFirstCertification != null) { removeLastFlag(identity, resourceKey); } dbInstance.getCurrentEntityManager().persist(certificate); } catch (Exception e) { log.error("", e); } return certificate; } @Override public void generateCertificates(List<CertificateInfos> certificateInfos, RepositoryEntry entry, CertificateTemplate template, boolean sendMail) { int count = 0; for(CertificateInfos certificateInfo:certificateInfos) { generateCertificate(certificateInfo, entry, template, sendMail); if(++count % 10 == 0) { dbInstance.commitAndCloseSession(); } } markPublisherNews(null, entry.getOlatResource()); } @Override public File previewCertificate(CertificateTemplate template, RepositoryEntry entry, Locale locale) { Identity identity = getPreviewIdentity(); File certificateFile; File dirFile = new File(WebappHelper.getTmpDir(), UUID.randomUUID().toString()); StringBuilder sb = new StringBuilder(); sb.append(Settings.getServerContextPathURI()).append("/certificate/") .append(UUID.randomUUID()).append("/preview.pdf"); String certUrl = sb.toString(); if(template == null) { CertificatePDFFormWorker worker = new CertificatePDFFormWorker(identity, entry, 2.0f, true, new Date(), new Date(), new Date(), certUrl, locale, userManager, this); certificateFile = worker.fill(null, dirFile, "Certificate.pdf"); } else if(template.getPath().toLowerCase().endsWith("pdf")) { CertificatePDFFormWorker worker = new CertificatePDFFormWorker(identity, entry, 2.0f, true, new Date(), new Date(), new Date(), certUrl, locale, userManager, this); certificateFile = worker.fill(template, dirFile, "Certificate.pdf"); } else { CertificatePhantomWorker worker = new CertificatePhantomWorker(identity, entry, 2.0f, true, new Date(), new Date(),new Date(), certUrl, locale, userManager, this); certificateFile = worker.fill(template, dirFile, "Certificate.pdf"); } return certificateFile; } private Identity getPreviewIdentity() { TransientIdentity identity = new TransientIdentity(); identity.setName("username"); List<UserPropertyHandler> userPropertyHandlers = userManager.getAllUserPropertyHandlers(); for(UserPropertyHandler handler:userPropertyHandlers) { if(handler instanceof DatePropertyHandler) { identity.getUser().setProperty(handler.getName(), Formatter.formatDatetime(new Date())); } else { identity.getUser().setProperty(handler.getName(), handler.getName()); } } return identity; } @Override public Certificate generateCertificate(CertificateInfos certificateInfos, RepositoryEntry entry, CertificateTemplate template, boolean sendMail) { Certificate certificate = persistCertificate(certificateInfos, entry, template, sendMail); markPublisherNews(null, entry.getOlatResource()); return certificate; } private Certificate persistCertificate(CertificateInfos certificateInfos, RepositoryEntry entry, CertificateTemplate template, boolean sendMail) { OLATResource resource = entry.getOlatResource(); Identity identity = certificateInfos.getAssessedIdentity(); CertificateImpl certificate = new CertificateImpl(); certificate.setOlatResource(resource); certificate.setArchivedResourceKey(resource.getKey()); if(certificateInfos.getCreationDate() != null) { certificate.setCreationDate(certificateInfos.getCreationDate()); } else { certificate.setCreationDate(new Date()); } certificate.setLastModified(certificate.getCreationDate()); certificate.setIdentity(identity); certificate.setUuid(UUID.randomUUID().toString()); certificate.setLast(true); certificate.setCourseTitle(entry.getDisplayname()); certificate.setStatus(CertificateStatus.pending); Date nextCertification = getDateNextRecertification(certificate, entry); certificate.setNextRecertificationDate(nextCertification); dbInstance.getCurrentEntityManager().persist(certificate); dbInstance.commit(); //send message sendJmsCertificateFile(certificate, template, certificateInfos.getScore(), certificateInfos.getPassed(), sendMail); return certificate; } protected VelocityEngine getVelocityEngine() { return velocityEngine; } private void sendJmsCertificateFile(Certificate certificate, CertificateTemplate template, Float score, Boolean passed, boolean sendMail) { QueueSender sender; QueueSession session = null; try { JmsCertificateWork workUnit = new JmsCertificateWork(); workUnit.setCertificateKey(certificate.getKey()); if(template != null) { workUnit.setTemplateKey(template.getKey()); } workUnit.setPassed(passed); workUnit.setScore(score); workUnit.setSendMail(sendMail); session = connection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE ); ObjectMessage message = session.createObjectMessage(); message.setObject(workUnit); sender = session.createSender(getJmsQueue()); sender.send( message ); } catch (JMSException e) { log.error("", e ); } finally { if(session != null) { try { session.close(); } catch (JMSException e) { //last hope } } } } @Override public void onMessage(Message message) { if(message instanceof ObjectMessage) { try { ObjectMessage objMsg = (ObjectMessage)message; JmsCertificateWork workUnit = (JmsCertificateWork)objMsg.getObject(); doCertificate(workUnit); message.acknowledge(); } catch (JMSException e) { log.error("", e); } finally { dbInstance.commitAndCloseSession(); } } } private void doCertificate(JmsCertificateWork workUnit) { CertificateImpl certificate = getCertificateById(workUnit.getCertificateKey()); CertificateTemplate template = null; if(workUnit.getTemplateKey() != null) { template = getTemplateById(workUnit.getTemplateKey()); } OLATResource resource = certificate.getOlatResource(); Identity identity = certificate.getIdentity(); RepositoryEntry entry = repositoryService.loadByResourceKey(resource.getKey()); String dir = usersStorage.generateDir(); File dirFile = new File(getCertificateRoot(), dir); dirFile.mkdirs(); Float score = workUnit.getScore(); String lang = identity.getUser().getPreferences().getLanguage(); Locale locale = I18nManager.getInstance().getLocaleOrDefault(lang); Boolean passed = workUnit.getPassed(); Date dateCertification = certificate.getCreationDate(); Date dateFirstCertification = getDateFirstCertification(identity, resource.getKey()); Date dateNextRecertification = certificate.getNextRecertificationDate(); File certificateFile; // File name with user name StringBuilder sb = new StringBuilder(); sb.append(identity.getUser().getProperty(UserConstants.LASTNAME, locale)).append("_") .append(identity.getUser().getProperty(UserConstants.FIRSTNAME, locale)).append("_") .append(entry.getDisplayname()).append("_") .append(Formatter.formatShortDateFilesystem(dateCertification)); String filename = FileUtils.normalizeFilename(sb.toString()) + ".pdf"; // External URL to certificate as short as possible for QR-Code sb = new StringBuilder(); sb.append(Settings.getServerContextPathURI()).append("/certificate/") .append(certificate.getUuid()).append("/certificate.pdf"); String certUrl = sb.toString(); if(template == null || template.getPath().toLowerCase().endsWith("pdf")) { CertificatePDFFormWorker worker = new CertificatePDFFormWorker(identity, entry, score, passed, dateCertification, dateFirstCertification, dateNextRecertification, certUrl, locale, userManager, this); certificateFile = worker.fill(template, dirFile, filename); if(certificateFile == null) { certificate.setStatus(CertificateStatus.error); } else { certificate.setStatus(CertificateStatus.ok); } } else { CertificatePhantomWorker worker = new CertificatePhantomWorker(identity, entry, score, passed, dateCertification, dateFirstCertification, dateNextRecertification, certUrl, locale, userManager, this); certificateFile = worker.fill(template, dirFile, filename); if(certificateFile == null) { certificate.setStatus(CertificateStatus.error); } else { certificate.setStatus(CertificateStatus.ok); } } certificate.setPath(dir + certificateFile.getName()); if(dateFirstCertification != null) { //not the first certification, reset the last of the others certificates removeLastFlag(identity, resource.getKey()); } MailerResult result = sendCertificate(identity, entry, certificateFile); if(result.isSuccessful()) { certificate.setEmailStatus(EmailStatus.ok); } else { certificate.setEmailStatus(EmailStatus.error); } dbInstance.getCurrentEntityManager().merge(certificate); dbInstance.commit(); CertificateEvent event = new CertificateEvent(identity.getKey(), certificate.getKey(), resource.getKey()); coordinatorManager.getCoordinator().getEventBus().fireEventToListenersOf(event, ORES_CERTIFICATE_EVENT); } private MailerResult sendCertificate(Identity to, RepositoryEntry entry, File certificateFile) { MailBundle bundle = new MailBundle(); bundle.setToId(to); bundle.setFrom(WebappHelper.getMailConfig("mailReplyTo")); List<String> bccs = certificatesModule.getCertificatesBccEmails(); if(bccs.size() > 0) { ContactList bcc = new ContactList(); bccs.forEach(email -> { bcc.add(email); }); bundle.setContactList(bcc); } String[] args = new String[] { entry.getDisplayname(), userManager.getUserDisplayName(to) }; String userLanguage = to.getUser().getPreferences().getLanguage(); Locale locale = i18nManager.getLocaleOrDefault(userLanguage); Translator translator = Util.createPackageTranslator(CertificateController.class, locale); String subject = translator.translate("certification.email.subject", args); String body = translator.translate("certification.email.body", args); bundle.setContent(subject, body, certificateFile); return mailManager.sendMessage(bundle); } private Date getDateFirstCertification(Identity identity, Long resourceKey) { StringBuilder sb = new StringBuilder(); sb.append("select cer.creationDate from certificate cer") .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey") .append(" order by cer.creationDate asc"); List<Date> dates = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Date.class) .setParameter("resourceKey", resourceKey) .setParameter("identityKey", identity.getKey()) .setMaxResults(1) .getResultList(); return dates.isEmpty() ? null : dates.get(0); } private void removeLastFlag(Identity identity, Long resourceKey) { StringBuilder sb = new StringBuilder(); sb.append("update certificate cer set cer.last=false") .append(" where cer.olatResource.key=:resourceKey and cer.identity.key=:identityKey"); dbInstance.getCurrentEntityManager().createQuery(sb.toString()) .setParameter("resourceKey", resourceKey) .setParameter("identityKey", identity.getKey()) .executeUpdate(); } @Override public List<CertificateTemplate> getTemplates() { String sb = "select template from certificatetemplate template where template.publicTemplate=true order by template.creationDate desc"; return dbInstance.getCurrentEntityManager() .createQuery(sb, CertificateTemplate.class) .getResultList(); } public CertificateTemplate getTemplateById(Long key) { String sb = "select template from certificatetemplate template where template.key=:templateKey"; List<CertificateTemplate> templates = dbInstance.getCurrentEntityManager() .createQuery(sb, CertificateTemplate.class) .setParameter("templateKey", key) .getResultList(); return templates.isEmpty() ? null : templates.get(0); } @Override public void deleteTemplate(CertificateTemplate template) { File templateFile = getTemplateFile(template); if(templateFile != null && templateFile.getParent() != null && templateFile.getParentFile().exists()) { try { FileUtils.deleteDirsAndFiles(templateFile.getParentFile().toPath()); } catch (IOException e) { log.error("", e); } } //delete in db CertificateTemplate reloadedTemplate = dbInstance.getCurrentEntityManager() .getReference(CertificateTemplateImpl.class, template.getKey()); dbInstance.getCurrentEntityManager().remove(reloadedTemplate); } @Override public CertificateTemplate addTemplate(String name, File file, String format, String orientation, boolean publicTemplate) { CertificateTemplateImpl template = new CertificateTemplateImpl(); template.setCreationDate(new Date()); template.setLastModified(template.getCreationDate()); template.setPublicTemplate(publicTemplate); template.setFormat(format); template.setOrientation(orientation); String filename = name.toLowerCase(); if(filename.endsWith(".pdf")) { if(addPdfTemplate(name, file, template)) { dbInstance.getCurrentEntityManager().persist(template); } else { template = null; } } else if(filename.endsWith(".zip")) { if(addHtmlTemplate(name, file, template)) { dbInstance.getCurrentEntityManager().persist(template); } else { template = null; } } else { template = null; } return template; } @Override public CertificateTemplate updateTemplate(CertificateTemplate template, String name, File file, String format, String orientation) { CertificateTemplateImpl templateToUpdate = (CertificateTemplateImpl)template; templateToUpdate.setLastModified(new Date()); templateToUpdate.setFormat(format); templateToUpdate.setOrientation(orientation); String filename = name.toLowerCase(); File templateFile = getTemplateFile(templateToUpdate); if(filename.endsWith(".pdf")) { if(addPdfTemplate(name, file, templateToUpdate)) { templateToUpdate = dbInstance.getCurrentEntityManager().merge(templateToUpdate); } else { templateToUpdate = null; } } else if(filename.endsWith(".zip")) { if(addHtmlTemplate(name, file, templateToUpdate)) { templateToUpdate = dbInstance.getCurrentEntityManager().merge(templateToUpdate); } else { templateToUpdate = null; } } if(templateToUpdate != null && templateFile != null && templateFile.exists()) { //if the new template is successfully saved, delete the old one FileUtils.deleteDirsAndFiles(templateFile.getParentFile(), true, true); } return templateToUpdate; } private boolean addHtmlTemplate(String name, File file, CertificateTemplateImpl template) { String dir = templatesStorage.generateDir(); VFSContainer templateDir = templatesStorage.getContainer(dir); try { File targetFolder = ((LocalFolderImpl)templateDir).getBasefile(); ZipUtils.unpackZip(file, targetFolder); template.setName(name); template.setPath(dir + "index.html"); return true; } catch (IOException e) { log.error("", e); return false; } } private boolean addPdfTemplate(String name, File file, CertificateTemplateImpl template) { String dir = templatesStorage.generateDir(); VFSContainer templateDir = templatesStorage.getContainer(dir); VFSLeaf templateLeaf; String renamedName = VFSManager.rename(templateDir, name); if(renamedName != null) { templateLeaf = templateDir.createChildLeaf(renamedName); } else { templateLeaf = templateDir.createChildLeaf(name); } try(InputStream inStream = Files.newInputStream(file.toPath())) { if(VFSManager.copyContent(inStream, templateLeaf)) { template.setName(name); template.setPath(dir + templateLeaf.getName()); return true; } } catch(IOException ex) { log.error("", ex); } return false; } @Override public File getTemplateFile(CertificateTemplate template) { String templatePath = template.getPath(); File root = getCertificateTemplatesRoot(); return new File(root, templatePath); } @Override public VFSLeaf getTemplateLeaf(CertificateTemplate template) { String templatePath = template.getPath(); VFSContainer root = this.getCertificateTemplatesRootContainer(); VFSItem templateItem = root.resolve(templatePath); return templateItem instanceof VFSLeaf ? (VFSLeaf)templateItem : null; } public InputStream getDefaultTemplate() { return CertificatesManager.class.getResourceAsStream("template.pdf"); } public File getCertificateTemplatesRoot() { Path path = Paths.get(folderModule.getCanonicalRoot(), "certificates", "templates"); File root = path.toFile(); if(!root.exists()) { root.mkdirs(); } return root; } public VFSContainer getCertificateTemplatesRootContainer() { return new OlatRootFolderImpl(File.separator + "certificates" + File.separator + "templates", null); } public File getCertificateRoot() { Path path = Paths.get(folderModule.getCanonicalRoot(), "certificates", "users"); File root = path.toFile(); if(!root.exists()) { root.mkdirs(); } return root; } public Path getRasterizePath() { Path path = Paths.get(folderModule.getCanonicalRoot(), "certificates", "rasterize.js"); return path; } public Path getQRCodeLibPath() { Path path = Paths.get(folderModule.getCanonicalRoot(), "certificates", "qrcode.min.js"); return path; } public VFSContainer getCertificateRootContainer() { return new OlatRootFolderImpl(File.separator + "certificates" + File.separator + "users", null); } }