/******************************************************************************* * Copyright (c) 2017 MEDEVIT. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * T. Huster - initial API and implementation *******************************************************************************/ package at.medevit.elexis.emediplan.core.internal; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.Base64; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Optional; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.apache.commons.io.IOUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.widgets.Display; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.osgi.service.component.annotations.Component; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import at.medevit.elexis.emediplan.core.EMediplanService; import at.medevit.elexis.emediplan.core.model.chmed16a.Medication; import ch.elexis.core.jdt.NonNull; import ch.elexis.core.services.IFormattedOutput; import ch.elexis.core.services.IFormattedOutputFactory; import ch.elexis.core.services.IFormattedOutputFactory.ObjectType; import ch.elexis.core.services.IFormattedOutputFactory.OutputType; import ch.elexis.data.Mandant; import ch.elexis.data.Patient; import ch.elexis.data.Prescription; @Component public class EMediplanServiceImpl implements EMediplanService { private Gson gson; public EMediplanServiceImpl(){ gson = new GsonBuilder().create(); } @Override public void exportEMediplanPdf(Mandant author, Patient patient, List<Prescription> prescriptions, OutputStream output){ if (prescriptions != null && !prescriptions.isEmpty() && output != null) { Optional<String> jsonString = getJsonString(author, patient, prescriptions); Optional<Image> qrCode = jsonString.map(json -> getQrCode(json)).orElse(Optional.empty()); Optional<at.medevit.elexis.emediplan.core.model.print.Medication> jaxbModel = getJaxbModel(author, patient, prescriptions); jaxbModel.ifPresent(model -> { createPdf(qrCode, model, output); }); } } private void createPdf(Optional<Image> qrCode, Object jaxbModel, OutputStream output){ BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); ServiceReference<IFormattedOutputFactory> fopFactoryRef = bundleContext.getServiceReference(IFormattedOutputFactory.class); if (fopFactoryRef != null) { IFormattedOutputFactory fopFactory = bundleContext.getService(fopFactoryRef); IFormattedOutput foOutput = fopFactory.getFormattedOutputImplementation(ObjectType.JAXB, OutputType.PDF); HashMap<String, String> parameters = new HashMap<>(); parameters.put("logoJpeg", getEncodedLogo()); qrCode.ifPresent(qr -> { parameters.put("qrJpeg", getEncodedQr(qr)); }); foOutput.transform(jaxbModel, EMediplanServiceImpl.class.getResourceAsStream("/rsc/xslt/emediplan.xslt"), output, parameters); bundleContext.ungetService(fopFactoryRef); } else { throw new IllegalStateException("No IFormattedOutputFactory available"); } } private String getEncodedQr(Image qr){ try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { ImageLoader imageLoader = new ImageLoader(); imageLoader.data = new ImageData[] { qr.getImageData() }; imageLoader.compression = 100; imageLoader.save(output, SWT.IMAGE_JPEG); return "data:image/jpg;base64," + Base64.getEncoder().encodeToString(output.toByteArray()); } catch (IOException e) { LoggerFactory.getLogger(getClass()).error("Error encoding QR", e); } return ""; } private String getEncodedLogo(){ try(InputStream input = getClass().getResourceAsStream("/rsc/img/Logo_Full.jpeg"); ByteArrayOutputStream output = new ByteArrayOutputStream()) { IOUtils.copy(input, output); return "data:image/jpg;base64," + Base64.getEncoder().encodeToString(output.toByteArray()); } catch (IOException e) { LoggerFactory.getLogger(getClass()).error("Error encoding logo", e); } return ""; } protected Optional<Image> getQrCode(@NonNull String json){ String encodedJson = getEncodedJson(json); Hashtable<EncodeHintType, Object> hintMap = new Hashtable<>(); hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); QRCodeWriter qrCodeWriter = new QRCodeWriter(); try { BitMatrix bitMatrix = qrCodeWriter.encode(encodedJson, BarcodeFormat.QR_CODE, 470, 470, hintMap); int width = bitMatrix.getWidth(); int height = bitMatrix.getHeight(); ImageData data = new ImageData(width, height, 24, new PaletteData(0xFF, 0xFF00, 0xFF0000)); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { data.setPixel(x, y, bitMatrix.get(x, y) ? 0x000000 : 0xFFFFFF); } } return Optional.of(new Image(Display.getDefault(), data)); } catch (WriterException e) { LoggerFactory.getLogger(getClass()).error("Error creating QR", e); return Optional.empty(); } } /** * Get the encoded (Header with zipped and Base64 encoded content) String. The header of the * current CHMED Version is added to the resulting String. * * @param json * @return */ protected String getEncodedJson(@NonNull String json){ StringBuilder sb = new StringBuilder(); // header for compresses json sb.append("CHMED16A1"); ByteArrayOutputStream out = new ByteArrayOutputStream(); try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { gzip.write(json.getBytes()); } catch (IOException e) { LoggerFactory.getLogger(getClass()).error("Error encoding json", e); throw new IllegalStateException("Error encoding json", e); } sb.append(Base64.getEncoder().encodeToString(out.toByteArray())); return sb.toString(); } /** * Get the decoded String, from the zipped and Base64 encoded String. The first 9 characters * (CHMED header) are ignored. * * @param encodedJson * @return */ protected String getDecodedJsonString(@NonNull String encodedJson){ String content = encodedJson.substring(9); byte[] zipped = Base64.getDecoder().decode(content); StringBuilder sb = new StringBuilder(); try { GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(zipped)); InputStreamReader reader = new InputStreamReader(gzip); BufferedReader in = new BufferedReader(reader); // Probably only single json line, but just to be sure ... String read; while ((read = in.readLine()) != null) { sb.append(read); } } catch (IOException e) { LoggerFactory.getLogger(getClass()).error("Error decoding json", e); throw new IllegalStateException("Error decoding json", e); } return sb.toString(); } protected Optional<at.medevit.elexis.emediplan.core.model.print.Medication> getJaxbModel( Mandant author, Patient patient, List<Prescription> prescriptions){ at.medevit.elexis.emediplan.core.model.print.Medication medication = at.medevit.elexis.emediplan.core.model.print.Medication.fromPrescriptions(author, patient, prescriptions); return Optional.ofNullable(medication); } protected Optional<String> getJsonString(Mandant author, Patient patient, List<Prescription> prescriptions){ Medication medication = Medication.fromPrescriptions(author, patient, prescriptions); return Optional.ofNullable(gson.toJson(medication)); } }