package org.agnitas.cms.utils.preview; import javax.imageio.*; import javax.servlet.http.*; import javax.swing.*; import java.awt.*; import java.awt.image.*; import java.beans.*; import java.io.*; import java.net.URL; import java.util.concurrent.*; import java.util.Map; import java.util.Vector; import org.agnitas.cms.utils.*; import org.agnitas.cms.utils.dataaccess.*; import org.agnitas.cms.web.*; import org.agnitas.cms.webservices.generated.*; import org.agnitas.util.*; import org.agnitas.dao.RecipientDao; import org.agnitas.dao.MailingDao; import org.agnitas.dao.MailingComponentDao; import org.agnitas.beans.MailingComponent; import org.agnitas.beans.Mailing; import org.apache.log4j.Logger; import org.springframework.context.*; /** * This class generate image from browser`s page. * Stores it image in database. */ public class PreviewImageGenerator { private static final transient Logger logger = Logger.getLogger( PreviewImageGenerator.class); JEditorPane editor; private boolean imagesLoaded = false; private boolean pageLoaded = false; private boolean rendered = false; private int initialWidth = 800; private int initialHeight = 600; private ApplicationContext aContext; private final HttpSession session; private int cmTemplateId; private int cmId; private int cmtId; private ThreadPoolExecutor threadPool; private int previewMaxWidth; private int previewMaxHeight; private static final int IMAGE_LOADING_TIMEOUT = 15; public PreviewImageGenerator(ApplicationContext applicationContext, HttpSession session, final int previewMaxWidth, final int previewMaxHeight) { if( session == null) { logger.error( "no session found for preview generation"); throw new NullPointerException( "SESSION is null"); } aContext = applicationContext; this.session = session; final int nThreads = 0; final long keepAliveTime = 0;//seconds final int maxPoolThreads = 1; final LinkedBlockingQueue<Runnable> runnables = new LinkedBlockingQueue<Runnable>(); threadPool = new ThreadPoolExecutor(nThreads, maxPoolThreads, keepAliveTime, TimeUnit.SECONDS, runnables); this.previewMaxWidth = previewMaxWidth; this.previewMaxHeight = previewMaxHeight; } /** * Generate preview image for one of cms`s element id, * element for wich generates preview must be non zero value and * other two must be equals to zero. * * @param cmTemplateId id of cms`s template * @param cmId id of content module * @param cmtId id of content module type */ public void generatePreview(int cmTemplateId, int cmId, int cmtId) { this.cmTemplateId = cmTemplateId; this.cmId = cmId; this.cmtId = cmtId; String previewUrl = generatePreviewUrl(cmTemplateId, cmId, cmtId); if(previewUrl == null) { return; } String systemUrl = AgnUtils.getEMMProperty("system.url"); final String finalPreviewUrl = systemUrl + previewUrl; if( logger.isInfoEnabled()) logger.info("HTML-preview URL is " + finalPreviewUrl); threadPool.execute(new Thread() { @Override public void run() { generatePreview(finalPreviewUrl, false); } }); } public void generatePreview(final Integer mailingId, final Integer companyId, boolean isUseThread) { generatePreview(mailingId, companyId, isUseThread, false); } public void generatePreview(final Integer mailingId, final Integer companyId, boolean isUseThread, final boolean bulkGenerate) { pageLoaded = false; imagesLoaded = false; if (isUseThread) { final Thread command = new Thread() { @Override public void run() { try { storeMailingPreview(mailingId, companyId, bulkGenerate); } catch( NullPointerException e) { logger.error( "Error generating preview", e); } } }; threadPool.execute(command); } else { try { storeMailingPreview(mailingId, companyId, bulkGenerate); } catch( NullPointerException e) { logger.error( "Error generating preview", e); } } } private void storeMailingPreview(Integer mailingId, Integer companyId, boolean bulkGenerate) { RecipientDao recipientDao = (RecipientDao) aContext.getBean("RecipientDao"); MailingDao mDao = (MailingDao) aContext.getBean("MailingDao"); MailingComponentDao componentDao = (MailingComponentDao) aContext.getBean("MailingComponentDao"); Mailing aMailing = (Mailing) mDao.getMailing(mailingId, companyId); final Map<Integer, String> testAdnAdminRecipients = recipientDao.getAdminAndTestRecipientsDescription(companyId, mailingId); if (!testAdnAdminRecipients.isEmpty()) { String previewUrl = "/mailingsend.do;jsessionid=" + session.getId() + "?action=16&mailingID=" + mailingId + "&previewFormat=1&previewCustomerID=" + (Integer) testAdnAdminRecipients.keySet().toArray()[0] + "&previewDay=0&previewMonth=0&previewYear=0"; String systemUrl = AgnUtils.getEMMProperty("system.url"); final Vector<MailingComponent> mailingComponents = componentDao.getMailingComponents(mailingId, companyId, MailingComponent.TYPE_THUMBMAIL_IMAGE); if (bulkGenerate){ previewUrl = previewUrl + "&previewCompanyId=" + companyId; session.setAttribute("bulkGenerate", "true"); } else { session.setAttribute("bulkGenerate", null); } final String finalPreviewUrl = systemUrl + previewUrl; if (!mailingComponents.isEmpty()) { MailingComponent component = mailingComponents.get(0); component.setBinaryBlock(generatePreview(finalPreviewUrl, true)); componentDao.saveMailingComponent(component); } else { MailingComponent component = (MailingComponent) aContext.getBean("MailingComponent"); component.setCompanyID(companyId); component.setMailingID(mailingId); component.setType(MailingComponent.TYPE_THUMBMAIL_IMAGE); component.setDescription("Mailing preview Image"); component.setComponentName("THUMBMAIL.png"); component.setBinaryBlock(generatePreview(finalPreviewUrl, true)); component.setMimeType("image/png"); component.setEmmBlock(component.makeEMMBlock()); aMailing.addComponent(component); mDao.saveMailing(aMailing); } } } byte[] generatePreview(String url,boolean isMailingPreview) { if( logger.isInfoEnabled()) logger.info("Trying to set headless mode"); System.setProperty("java.awt.headless", "true"); if( logger.isInfoEnabled()) logger.info("Creating swing html editor"); editor = new JEditorPane(); CmsEditorKit editorKit = new CmsEditorKit() { @Override public void onImagesLoaded() { imagesLoaded = true; if( logger.isInfoEnabled()) logger.info("preview`s image load finished"); } }; editor.setEditorKit(editorKit); editor.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if("page".equals(event.getPropertyName())) { onPageLoaded(); } } }); try { URL page = new URL(url); if( "https".equalsIgnoreCase( page.getProtocol())) { URL realUrl = new URL(null, url, new TrustedHttpsHandler()); editor.setPage(realUrl); } else { editor.setPage(url); } } catch(IOException e) { logger.error("URL for preview generation is not valid: " + url, e); } int secondCounter = 0; while (!(pageLoaded && imagesLoaded)) { try { Thread.sleep(1000); } catch (InterruptedException e) { logger.error("preview generation failed!!", e); } secondCounter++; if (secondCounter > IMAGE_LOADING_TIMEOUT) break; } return renderPreview(isMailingPreview); } private void onPageLoaded() { editor.setDoubleBuffered(false); editor.setSize(initialWidth, initialHeight); editor.addNotify(); editor.validate(); BufferedImage dummyImage = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB); Graphics2D imageGraphics = dummyImage.createGraphics(); editor.paint(imageGraphics); pageLoaded = true; if(!imagesLoaded) { imagesLoaded = ((CmsEditorKit) editor.getEditorKit()).getImageCount() == 0; } if( logger.isInfoEnabled()) logger.info("page for generation preview was loaded"); } private byte[] renderPreview(boolean isMailingPreview) { if(imagesLoaded && pageLoaded && !rendered) { if( logger.isInfoEnabled()) logger.info("preview`s image rendering started..."); // determine rendering page size Dimension preferredSize = editor.getPreferredSize(); Dimension minimumSize = editor.getMinimumSize(); Dimension currentSize = editor.getSize(); if(minimumSize.width > currentSize.width || minimumSize.height > currentSize.height) { currentSize.width = minimumSize.width; currentSize.height = minimumSize.height; } else if(preferredSize.width < currentSize.width) { currentSize.width = preferredSize.width; currentSize.height = preferredSize.height; } else if(preferredSize.height < currentSize.height) { currentSize.height = preferredSize.height; } // relayout page as we have now the new size and all images loaded editor.setSize(currentSize.width, currentSize.height); editor.addNotify(); editor.validate(); // paint page on image BufferedImage originalImage = new BufferedImage(currentSize.width, currentSize.height, BufferedImage.TYPE_INT_RGB); Graphics2D imageGraphics = originalImage.createGraphics(); editor.paint(imageGraphics); // scale image Dimension previewSize = getPreviewSize(currentSize.width, currentSize.height, isMailingPreview); BufferedImage resultImage; if(previewSize.width > currentSize.width) { resultImage = originalImage; } else { BufferedImage croppedImage = cropImage(originalImage, currentSize.width, currentSize.height, isMailingPreview); Image scaledImage = croppedImage.getScaledInstance(previewSize.width, previewSize.height, Image.SCALE_SMOOTH); resultImage = new BufferedImage(previewSize.width, previewSize.height, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = resultImage.createGraphics(); graphics.drawImage(scaledImage, 0, 0, null); graphics.dispose(); } try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ImageIO.write(resultImage, "png", byteArrayOutputStream); byte[] imageData = byteArrayOutputStream.toByteArray(); if (isMailingPreview) { return imageData; } else { storePreview(imageData); return null; } } catch(IOException e) { logger.error("Error occurred while saving preview-image", e); } if( logger.isInfoEnabled()) logger.info("preview`s image rendering finished"); rendered = true; } return null; } private BufferedImage cropImage(BufferedImage originalImage, int width, int height, boolean mailingPreview) { if (!mailingPreview) return originalImage; double scaleX = ((double) previewMaxWidth) / ((double) width); double scaleY = ((double) previewMaxHeight) / ((double) height); int cropX = width - 1; int cropY = height - 1; if (scaleY > scaleX) { cropX = (int) (width * (scaleX / scaleY)); } else { cropY = (int) (height * (scaleY / scaleX)); } return originalImage.getSubimage(0, 0, cropX, cropY); } private void storePreview(byte[] imageData) { MediaFileManager mediaFileManager = CmsUtils.getMediaFileManager(aContext); MediaFile mediaFile = new MediaFile(cmTemplateId, 1, imageData, cmId, cmtId, 0, MediaFileUtils.PREVIEW_TYPE, "image/png", "preview"); if(cmId != 0) { mediaFileManager.removePreviewOfContentModule(cmId); } else if(cmtId != 0) { mediaFileManager.removePreviewOfContentModuleType(cmtId); } else if(cmTemplateId != 0) { mediaFileManager.removePreviewOfContentModuleTemplate(cmTemplateId); } mediaFileManager.createMediaFile(mediaFile); } private Dimension getPreviewSize(int originalWidth, int originalHeight, boolean isMailingPreview) { if (isMailingPreview) return new Dimension(previewMaxWidth, previewMaxHeight); double scaleX = ((double) previewMaxWidth) / ((double) originalWidth); double scaleY = ((double) previewMaxHeight) / ((double) originalHeight); double scale = Math.min(scaleX, scaleY); return new Dimension((int) (scale * originalWidth), (int) (scale * originalHeight)); } private String generatePreviewUrl(int cmTemplateId, int cmId, int cmtId) { String sessionId = session.getId(); if(cmId > 0) { return "/cms_contentmodule.do;jsessionid=" + sessionId + "?action=" + ContentModuleAction.ACTION_PURE_PREVIEW + "&contentModuleId=" + cmId; } else if(cmTemplateId > 0) { return "/cms_cmtemplate.do;jsessionid=" + sessionId + "?action=" + CMTemplateAction.ACTION_PURE_PREVIEW + "&cmTemplateId=" + cmTemplateId; } else if(cmtId > 0) { return "/cms_cmt.do;jsessionid=" + sessionId + "?action=" + ContentModuleTypeAction.ACTION_PURE_PREVIEW + "&cmtId=" + cmtId; } else { return null; } } }