All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.dam.handler.standard.ooxml.MSPowerPointOOXMLHandler Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * ___________________
 *
 *  Copyright 2013 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.day.cq.dam.handler.standard.ooxml;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.concurrent.Callable;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;

import com.adobe.granite.asset.api.AssetException;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.dam.api.DamConstants;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.poi.POIXMLProperties;
import org.apache.poi.xslf.usermodel.XSLFTheme;
import org.apache.poi.xslf.usermodel.XSLFNotes;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xslf.usermodel.XSLFSlideLayout;
import org.apache.poi.xslf.usermodel.XSLFSlideMaster;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.asset.api.AssetManager;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.image.Layer;

/**
 * Asset Handler for Open XML PPTX files, handles sub assets for PPTX as well.
 *
 */
@Component(inherit = true, metatype = false)
@Service
public class MSPowerPointOOXMLHandler extends OpenOfficeHandler {
	private static final String SUB_ASSET_PREFIX = "slide_";

	private static final String PPTX_EXT = ".pptx";

	private static final Logger log = LoggerFactory.getLogger(MSPowerPointOOXMLHandler.class);

	public static final String MIMETYPE_PPTX
    			= "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    public static final String DEFAULT_PAGES_REGEX = "^slide_[0-9]*.pptx";

    @Property(name = "cq.dam.pptx.pages.regex", value= DEFAULT_PAGES_REGEX, label = "pptx pages regex", description = "Regex for identifying pages in subassets folder. The pages will be shown in assets page viewer")
    public static final String PAGES_REGEX = "cq.dam.pptx.pages.regex";

    private String pagesRegex;

	@Override
	public boolean canHandleSubAssets() {
		 return true;
	}
	
	/**
     * {@inheritDoc}
     */
	@Override
    public String[] getMimeTypes() {
        return new String[]{MIMETYPE_PPTX};
    }
	
	private void removeSubAssets(final Asset asset, AssetManager assetManager) {
        //cleanup subassts
        Collection subAssets = asset.getSubAssets();
        for (Asset subAsset:subAssets) {
            assetManager.removeAsset(subAsset.getPath());
        }
    }
    
	/**
	 * {@inheritDoc}
	 */
	@Override
    public List processSubAssets(final Asset asset) {
		List subAssets = new ArrayList();
        if (asset.isSubAsset()) {
            // we do not continue processing here, otherwise we would enter an
            // endless processing stack
            return subAssets;
        }

        InputStream is = null;
        
        try {
            is = asset.getOriginal().getStream();
            
            // changes made to the asset are not saved until manually later on.
            final boolean oldBatchMode = asset.isBatchMode();
            asset.setBatchMode(true);
            AssetManager assetManager = asset.getOriginal().getResourceResolver().adaptTo(AssetManager.class);
            removeSubAssets(asset, assetManager);
            XMLSlideShow slideShow = new XMLSlideShow(is);
            int i=0;
            for (XSLFSlide srcSlide : slideShow.getSlides())  {
            	int slideNumber = ++i;
            	XMLSlideShow extractedSlide = extractSlideByIndex(slideShow, asset, slideNumber);
				FileOutputStream itout = null;
                File pptTmpFile = null;
                InputStream iis = null;
				try {
					pptTmpFile = File.createTempFile("pptx", ".tmp");
					itout = FileUtils.openOutputStream(pptTmpFile);
					extractedSlide.write(itout);
					String fileName = SUB_ASSET_PREFIX + slideNumber + PPTX_EXT;
					iis = FileUtils.openInputStream(pptTmpFile);
                    Asset subAsset = asset.addSubAsset(fileName, asset.getMimeType(), iis);
                    subAssets.add(subAsset.getPath());
				} catch (IOException e) {
		        	log.warn("error extracting subassets from asset {0} reason {1}", asset.getPath(), e.getMessage());
		            if (log.isDebugEnabled()) {
		                log.debug("Stack Trace", e);
		            }
				} finally {
					IOUtils.closeQuietly(iis);
                    IOUtils.closeQuietly(itout);
                    FileUtils.deleteQuietly(pptTmpFile);
				}
			}
            
            // now save the changes made to the asset.
            asset.adaptTo(Node.class).getSession().save();
            asset.setBatchMode(oldBatchMode);
        } catch (IOException e) {
        	log.warn("error parsing asset {0} reason {1}", asset.getPath(), e.getMessage());
            if (log.isDebugEnabled()) {
                log.debug("Stack Trace", e);
            }
		} catch (RepositoryException e) {
			log.warn("error parsing asset {0} reason {1}", asset.getPath(), e.getMessage());
            if (log.isDebugEnabled()) {
                log.debug("Stack Trace", e);
            }
		} finally {
			IOUtils.closeQuietly(is);
		}

        cleanup(asset);

        updatePageRelations(asset);
        
        return subAssets;
	}

    private void cleanup(final Asset asset) {
        com.adobe.granite.asset.api.AssetManager assetManager = asset.getOriginal().getResourceResolver().adaptTo(
                com.adobe.granite.asset.api.AssetManager.class);
        // clean up pages relation
        com.adobe.granite.asset.api.Asset graniteAsset = asset.adaptTo(com.adobe.granite.asset.api.Asset.class);
        try {
            graniteAsset.removeRelation(DamConstants.RELATION_ASSET_PAGES);
        } catch (AssetException ae) {
            log.debug("Exception occured while deleting "
                    + DamConstants.RELATION_ASSET_PAGES + " relation", ae);
        }

    }

    /**
     * creates pages relation for the asset with the subassets that matches
     * pagesRegex
     *
     * @param asset
     */
    private void updatePageRelations(Asset asset) {
        Collection subAssets = asset.getSubAssets();
        int numPages = 0;
        if (!subAssets.isEmpty()) {
            com.adobe.granite.asset.api.Asset graniteAsset = asset.adaptTo(com.adobe.granite.asset.api.Asset.class);
            for (Asset subAsset : subAssets) {
                if (subAsset.getName().matches(pagesRegex)) {
                    graniteAsset.addRelation(DamConstants.RELATION_ASSET_PAGES,
                            subAsset.getPath());
                    numPages++;
                }
            }
            if (numPages > 0) {
                ResourceResolver resolver = graniteAsset.getResourceResolver();
                Node assetNode = resolver.getResource(asset.getPath()).adaptTo(
                        Node.class);
                try {
                    Node metadataNode = assetNode.getNode(JcrConstants.JCR_CONTENT
                            + "/" + DamConstants.METADATA_FOLDER);
                    metadataNode.setProperty("dam:numPages", numPages);
                } catch (PathNotFoundException e) {
                    log.warn("Unable to set dam:numPages on " + asset.getPath());
                    log.debug("Exception while setting dam:numPages on "
                            + asset.getPath(), e);
                } catch (RepositoryException e) {
                    log.warn("Unable to set dam:numPages on " + asset.getPath());
                    log.debug("Exception while setting dam:numPages on "
                            + asset.getPath(), e);
                }
            }

        }
    }
	
	/**
     * To extract each slide, we need to pull each slide from source PPTX and then save it in a new pptx.
     * This process must be repeated for each slide that we want to extract.
     * 
     * extractSlideByIndex extracts a slide, given the slideNumber (starts at 1)
     */
    private XMLSlideShow extractSlideByIndex(final XMLSlideShow slideShow, final Asset asset, int slideNumber) {
        XMLSlideShow slideShowNew =  new XMLSlideShow();

        try {

            slideShowNew.setPageSize(slideShow.getPageSize()); // required for thumbnail generation else thumbnails would be generated with default pageSize
            final List slides = slideShow.getSlides();
            int i=1;
            for(XSLFSlide srcSlide : slides){

                if(i==slideNumber) {
                    XSLFSlideLayout src_layout =srcSlide.getSlideLayout();
                    XSLFSlideMaster src_master = srcSlide.getSlideMaster();
                    XSLFTheme src_theme = srcSlide.getTheme();
                    XSLFNotes src_notes = srcSlide.getNotes();

                    XSLFSlide newSlide = slideShowNew.createSlide();
                    if (src_master != null ) newSlide.getSlideMaster().importContent(src_master);
                    if (src_theme != null )newSlide.getTheme().importTheme(src_theme);
                    if (src_layout != null ) newSlide.getSlideLayout().importContent(src_layout);
                    //Add notes to the slide from source slide : only available since poi 3.11
                    XSLFNotes newNotesSlide = slideShowNew.getNotesSlide(newSlide);
                    if (src_notes != null )newNotesSlide.importContent(src_notes);
                    try {
                        // Add Core Properties to new pptx subasset
                        POIXMLProperties src_properties = slideShow.getProperties();
                        if (src_properties != null) {
                            slideShowNew.getProperties().getCoreProperties().setTitle(SUB_ASSET_PREFIX + slideNumber + "_" + src_properties.getCoreProperties().getTitle());
                            slideShowNew.getProperties().getCoreProperties().setDescription(src_properties.getCoreProperties().getDescription());
                            slideShowNew.getProperties().getCoreProperties().setCreated(getFormattedDate(src_properties.getCoreProperties().getCreated()));
                            slideShowNew.getProperties().getCoreProperties().setCreator(src_properties.getCoreProperties().getCreator());
                            slideShowNew.getProperties().getCoreProperties().setModified(getFormattedDate(src_properties.getCoreProperties().getModified()));
                            slideShowNew.getProperties().getCoreProperties().setRevision(src_properties.getCoreProperties().getRevision());
                            slideShowNew.getProperties().getCoreProperties().setContentType(src_properties.getCoreProperties().getContentType());
                            slideShowNew.getProperties().getCoreProperties().setCategory(src_properties.getCoreProperties().getCategory());
                            slideShowNew.getProperties().getCoreProperties().setKeywords(src_properties.getCoreProperties().getKeywords());
                        }
                    }
                    catch (Exception e) {
                        log.debug("extractSlideByIndex : Error extracting core properties for slide number "+ Integer.toString(slideNumber) +" from asset "+ asset.getPath() + " reason "+  e.getMessage());
                    }
                    newSlide.importContent(srcSlide); // import the actual slide content here
                    break;
                }
                i++;
            }
        } catch (Exception e) {
            log.warn("extractSlideByIndex : Error extracting explicit slide number "+ Integer.toString(slideNumber) +" from asset "+ asset.getPath() + " reason "+  e.getMessage());
            if (log.isDebugEnabled()) {
                log.debug("Stack Trace", e);
            }
        }
        return slideShowNew;
    }
    @Override
    public BufferedImage getImage(final Rendition rendition, final Dimension dim) throws IOException {
        try {
            return callWithThreadContextClassLoader(new Callable() {
                public BufferedImage call() throws Exception {
                    return MSPowerPointOOXMLHandler.this.dogetImage(rendition, dim);
                }
            });
        } catch (Exception e) {
            log.error("getImage: Cannot read image from {}: {}", rendition.getPath(), e.getMessage());
        }

        return null;
    }

    public BufferedImage dogetImage(final Rendition rendition, Dimension dim) throws IOException {
        final InputStream is = rendition.getStream();
        final XMLSlideShow slideShow = new XMLSlideShow(is);
        final List slides = slideShow.getSlides();

        if (slides != null && slides.size() > 0) {
            // we need to override the dimension to match slide dimension
            dim = slideShow.getPageSize();
            try {
                BufferedImage image = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_RGB);
                Graphics2D graphics = image.createGraphics();
                slides.get(0).draw(graphics); // draw first slide as a thumbnail
                return new Layer(image).getImage();
            } catch (Exception e) {
                log.warn("getImage: error while getting image for {} reason: {}", rendition.getPath(), e.getMessage());
                if (log.isDebugEnabled()) {
                    log.debug("Stack Trace", e);
                }
            } finally {
                IOUtils.closeQuietly(is);
            }
        }
        IOUtils.closeQuietly(is);
        return null;
    }

    @Activate
    private void activate(Map config) throws IOException {
        pagesRegex = OsgiUtil.toString(config.get(PAGES_REGEX),
                DEFAULT_PAGES_REGEX);

    }
    private DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
 private String getFormattedDate(Date dt){
        return df.format(dt);
 }
    // Check:- http://stackoverflow.com/questions/1043109/why-cant-jaxb-find-my-jaxb-index-when-running-inside-apache-felix
    private  T callWithThreadContextClassLoader(Callable callable) throws Exception {
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(org.apache.poi.sl.draw.binding.ObjectFactory.class.getClassLoader());
        try {
            return callable.call();
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy