/**
* <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.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.velocity.VelocityContext;
import org.olat.core.id.Identity;
import org.olat.core.id.User;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.course.certificate.CertificateTemplate;
import org.olat.repository.RepositoryEntry;
import org.olat.user.UserManager;
import org.olat.user.propertyhandlers.UserPropertyHandler;
/**
*
* Initial date: 16.11.2014<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class CertificatePhantomWorker {
private static final OLog log = Tracing
.createLoggerFor(CertificatePDFFormWorker.class);
private final Float score;
private final Boolean passed;
private final Identity identity;
private final RepositoryEntry entry;
private final String certificateURL;
private Date dateCertification;
private Date dateFirstCertification;
private Date dateNextRecertification;
private final Locale locale;
private final UserManager userManager;
private final CertificatesManagerImpl certificatesManager;
public CertificatePhantomWorker(Identity identity, RepositoryEntry entry,
Float score, Boolean passed, Date dateCertification,
Date dateFirstCertification, Date nextRecertificationDate, String certificateURL, Locale locale,
UserManager userManager, CertificatesManagerImpl certificatesManager) {
this.entry = entry;
this.score = score;
this.locale = locale;
this.passed = passed;
this.identity = identity;
this.dateCertification = dateCertification;
this.dateFirstCertification = dateFirstCertification;
this.dateNextRecertification = nextRecertificationDate;
this.certificateURL = certificateURL;
this.userManager = userManager;
this.certificatesManager = certificatesManager;
}
public File fill(CertificateTemplate template, File destinationDir, String filename) {
File certificateFile = new File(destinationDir, filename);
File templateFile = certificatesManager.getTemplateFile(template);
File htmlCertificateFile = copyAndEnrichTemplate(templateFile);
List<String> cmds = new ArrayList<String>();
cmds.add("phantomjs");
cmds.add(certificatesManager.getRasterizePath().toFile().getAbsolutePath());
cmds.add(htmlCertificateFile.getAbsolutePath());
cmds.add(certificateFile.getAbsolutePath());
if(StringHelper.containsNonWhitespace(template.getFormat())) {
cmds.add(template.getFormat());
} else {
cmds.add("A4");
}
if(StringHelper.containsNonWhitespace(template.getOrientation())) {
cmds.add(template.getOrientation());
} else {
cmds.add("portrait");
}
CountDownLatch doneSignal = new CountDownLatch(1);
ProcessWorker worker = new ProcessWorker(cmds, htmlCertificateFile, doneSignal);
worker.start();
try {
doneSignal.await(30000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("", e);
}
worker.destroyProcess();
return certificateFile;
}
private File copyAndEnrichTemplate(File templateFile) {
VelocityContext context = getContext();
boolean result = false;
File htmlCertificate = new File(templateFile.getParent(), "c" + UUID.randomUUID() + ".html");
try(Reader in = Files.newBufferedReader(templateFile.toPath(), Charset.forName("UTF-8"));
Writer output = new FileWriter(htmlCertificate)) {
result = certificatesManager.getVelocityEngine().evaluate(context, output, "mailTemplate", in);
output.flush();
} catch(Exception e) {
log.error("", e);
}
return result ? htmlCertificate : null;
}
private VelocityContext getContext() {
VelocityContext context = new VelocityContext();
fillUserProperties(context);
fillRepositoryEntry(context);
fillCertificationInfos(context);
fillAssessmentInfos(context);
fillMetaInfos(context);
return context;
}
private void fillUserProperties(VelocityContext context) {
User user = identity.getUser();
List<UserPropertyHandler> userPropertyHandlers = userManager.getAllUserPropertyHandlers();
for (UserPropertyHandler handler : userPropertyHandlers) {
String propertyName = handler.getName();
String value = handler.getUserProperty(user, null);
context.put(propertyName, value);
}
String fullName = userManager.getUserDisplayName(identity);
context.put("fullName", fullName);
String firstName = user.getProperty(UserConstants.FIRSTNAME, null);
String lastName = user.getProperty(UserConstants.LASTNAME, null);
StringBuilder firstNameLastName = new StringBuilder();
StringBuilder lastNameFirstName = new StringBuilder();
if(StringHelper.containsNonWhitespace(firstName)) {
firstNameLastName.append(firstName);
}
if(StringHelper.containsNonWhitespace(lastName)) {
if(firstNameLastName.length() > 0) firstNameLastName.append(" ");
firstNameLastName.append(lastName);
lastNameFirstName.append(lastName);
}
if(StringHelper.containsNonWhitespace(firstName)) {
if(lastNameFirstName.length() > 0) lastNameFirstName.append(" ");
lastNameFirstName.append(firstName);
}
context.put("firstNameLastName", firstNameLastName.toString());
context.put("lastNameFirstName", lastNameFirstName.toString());
}
private void fillRepositoryEntry(VelocityContext context) {
String title = entry.getDisplayname();
context.put("title", title);
String externalRef = entry.getExternalRef();
context.put("externalReference", externalRef);
String authors = entry.getAuthors();
context.put("authors", authors);
String expenditureOfWorks = entry.getExpenditureOfWork();
context.put("expenditureOfWorks", expenditureOfWorks);
String mainLanguage = entry.getMainLanguage();
context.put("mainLanguage", mainLanguage);
if (entry.getLifecycle() != null) {
Formatter format = Formatter.getInstance(locale);
Date from = entry.getLifecycle().getValidFrom();
String formattedFrom = format.formatDate(from);
context.put("from", formattedFrom);
String formattedFromLong = format.formatDateLong(from);
context.put("fromLong", formattedFromLong);
Date to = entry.getLifecycle().getValidTo();
String formattedTo = format.formatDate(to);
context.put("to", formattedTo);
String formattedToLong = format.formatDateLong(to);
context.put("toLong", formattedToLong);
}
}
private void fillCertificationInfos(VelocityContext context) {
Formatter format = Formatter.getInstance(locale);
context.put("dateFormatter", format);
if(dateCertification == null) {
context.put("dateCertification", "");
} else {
String formattedDateCertification = format.formatDate(dateCertification);
context.put("dateCertification", formattedDateCertification);
String formattedDateCertificationLong = format.formatDateLong(dateCertification);
context.put("dateCertificationLong", formattedDateCertificationLong);
context.put("dateCertificationRaw", dateCertification);
}
if(dateFirstCertification == null) {
context.put("dateFirstCertification", "");
} else {
String formattedDateFirstCertification = format.formatDate(dateFirstCertification);
context.put("dateFirstCertification", formattedDateFirstCertification);
String formattedDateFirstCertificationLong = format.formatDate(dateFirstCertification);
context.put("dateFirstCertificationLong", formattedDateFirstCertificationLong);
context.put("dateFirstCertificationRaw", dateFirstCertification);
}
if(dateNextRecertification == null) {
context.put("dateNextRecertification", "");
} else {
String formattedDateNextRecertification = format.formatDate(dateNextRecertification);
context.put("dateNextRecertification", formattedDateNextRecertification);
String formattedDateNextRecertificationLong = format.formatDateLong(dateNextRecertification);
context.put("dateNextRecertificationLong", formattedDateNextRecertificationLong);
context.put("dateNextRecertificationRaw", dateNextRecertification);
}
}
private void fillAssessmentInfos(VelocityContext context) {
String roundedScore = AssessmentHelper.getRoundedScore(score);
context.put("score", roundedScore);
String status = (passed != null && passed.booleanValue()) ? "Passed" : "Failed";
context.put("status", status);
}
private void fillMetaInfos(VelocityContext context) {
context.put("certificateVerificationUrl", certificateURL);
}
public static boolean checkPhantomJSAvailabilty() {
List<String> cmds = new ArrayList<String>();
cmds.add("phantomjs");
cmds.add("--help");
CountDownLatch doneSignal = new CountDownLatch(1);
ProcessWorker worker = new ProcessWorker(cmds, null, doneSignal);
worker.start();
try {
doneSignal.await(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
log.error("", e);
}
log.info("PhantomJS help is available if exit value = 0: " + worker.getExitValue());
return worker.getExitValue() == 0;
}
private static class ProcessWorker extends Thread {
private volatile Process process;
private int exitValue = -1;
private final List<String> cmd;
private final CountDownLatch doneSignal;
private final File htmlCertificateFile;
public ProcessWorker(List<String> cmd, File htmlCertificateFile, CountDownLatch doneSignal) {
this.cmd = cmd;
this.doneSignal = doneSignal;
this.htmlCertificateFile = htmlCertificateFile;
}
public void destroyProcess() {
if (process != null) {
process.destroy();
process = null;
}
}
public int getExitValue() {
return exitValue;
}
@Override
public void run() {
try {
if(log.isDebug()) {
log.debug(cmd.toString());
}
ProcessBuilder builder = new ProcessBuilder(cmd);
process = builder.start();
executeProcess(process);
doneSignal.countDown();
} catch (IOException e) {
log.error ("Could not spawn convert sub process", e);
destroyProcess();
} finally {
if(htmlCertificateFile != null) {
htmlCertificateFile.delete();
}
}
}
private final void executeProcess(Process proc) {
StringBuilder errors = new StringBuilder();
StringBuilder output = new StringBuilder();
String line;
InputStream stderr = proc.getErrorStream();
InputStreamReader iserr = new InputStreamReader(stderr);
BufferedReader berr = new BufferedReader(iserr);
line = null;
try {
while ((line = berr.readLine()) != null) {
errors.append(line);
}
} catch (IOException e) {
//
}
InputStream stdout = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdout);
BufferedReader br = new BufferedReader(isr);
line = null;
try {
while ((line = br.readLine()) != null) {
output.append(line);
}
} catch (IOException e) {
//
}
if(log.isDebug()) {
log.debug("Error: " + errors.toString());
log.debug("Output: " + output.toString());
}
try {
exitValue = proc.waitFor();
if (exitValue != 0) {
log.warn("Problem with PhantomJS? " + exitValue);
}
} catch (InterruptedException e) {
log.warn("Takes too long");
}
}
}
}