001/* 002 * Copyright 2014 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.flipbook; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.util.concurrent.ConcurrentHashMap; 023import java.util.concurrent.ConcurrentMap; 024import java.util.concurrent.locks.ReentrantLock; 025 026import org.apache.avalon.framework.activity.Initializable; 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.logger.AbstractLogEnabled; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.avalon.framework.thread.ThreadSafe; 033import org.apache.commons.io.FileUtils; 034import org.apache.commons.io.IOUtils; 035 036import org.ametys.plugins.repository.AmetysObjectResolver; 037import org.ametys.runtime.servlet.RuntimeConfig; 038 039/** 040 * Document to images action: converts the document into one image per page if it is not already done. 041 */ 042public abstract class AbstractConvertDocument2ImagesComponent extends AbstractLogEnabled implements ThreadSafe, Initializable, Serviceable, Component 043{ 044 /** The ametys object resolver. */ 045 protected AmetysObjectResolver _resolver; 046 047 /** The document to images convertor. */ 048 protected Document2ImagesConvertor _documentToImages; 049 050 /** The cocoon context */ 051 protected org.apache.cocoon.environment.Context _cocoonContext; 052 053 /** Map of locks, indexed by resource ID. */ 054 private ConcurrentMap<String, ReentrantLock> _locks; 055 056 @Override 057 public void initialize() throws Exception 058 { 059 _locks = new ConcurrentHashMap<>(); 060 } 061 062 @Override 063 public void service(ServiceManager serviceManager) throws ServiceException 064 { 065 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 066 _documentToImages = (Document2ImagesConvertor) serviceManager.lookup(Document2ImagesConvertor.ROLE); 067 } 068 069 /** 070 * Test if the mimetype is supported for conversion 071 * @param mimeType the mime type 072 * @return true if the mimetype can be transformed 073 */ 074 protected boolean isMimeTypeSupported(String mimeType) 075 { 076 return _documentToImages.getSupportedMimeTypes().contains(mimeType); 077 } 078 079 /** 080 * Create the images and put it into the cache 081 * @param relativeCachePath the path of the relative cache 082 * @param md5sum the md5 sum 083 * @param documentInputStream the input stream of the document 084 * @param documentName the document's name 085 * @param documentId the id of the document 086 * @param documentMimeType the mime type of the document 087 * @return The absolute cache path 088 * @throws IOException if an error occurs while manipulating files 089 * @throws FlipbookException if an error occurs while manipulating the flipbook 090 * @throws UnsupportedOperationException If the mime type is not supported 091 */ 092 protected String cache(String relativeCachePath, String md5sum, InputStream documentInputStream, String documentName, String documentId, String documentMimeType) throws IOException, FlipbookException 093 { 094 if (!isMimeTypeSupported(documentMimeType)) 095 { 096 throw new UnsupportedOperationException("Cannot convert files of type '" + documentMimeType + "'"); 097 } 098 099 File baseFolder = FileUtils.getFile(RuntimeConfig.getInstance().getAmetysHome(), "flipbook", relativeCachePath); 100 if (!baseFolder.exists()) 101 { 102 baseFolder.mkdirs(); 103 } 104 105 String cachePath = baseFolder.getPath().replace('\\', '/'); 106 107 File md5File = new File(baseFolder, "document.md5"); 108 if (!md5File.isFile()) 109 { 110 md5File.createNewFile(); 111 } 112 // Compare the two 113 String oldMd5 = FileUtils.readFileToString(md5File, "UTF-8"); 114 115 File documentFile = new File(baseFolder, "document/" + documentName); 116 117 if (!md5sum.equals(oldMd5) || !documentFile.isFile()) 118 { 119 ReentrantLock lock = new ReentrantLock(); 120 121 ReentrantLock oldLock = _locks.putIfAbsent(documentId, lock); 122 if (oldLock != null) 123 { 124 lock = oldLock; 125 } 126 127 lock.lock(); 128 129 try 130 { 131 // Test the md5 again, perhaps it was generated by another thread while we were locked. 132 oldMd5 = FileUtils.readFileToString(md5File, "UTF-8"); 133 134 if (!md5sum.equals(oldMd5) || !documentFile.isFile()) 135 { 136 try 137 { 138 createImages(documentInputStream, documentName, baseFolder); 139 } 140 catch (Throwable t) 141 { 142 getLogger().error("An error occured while converting the document to images \"" + documentFile.getAbsolutePath() + "\"", t); 143 throw new FlipbookException("An error occured while converting the document to images \"" + documentFile.getAbsolutePath() + "\"", t); 144 } 145 } 146 147 // MD5 148 FileUtils.writeStringToFile(md5File, md5sum, "UTF-8"); 149 } 150 finally 151 { 152 lock.unlock(); 153 154 if (!lock.hasQueuedThreads()) 155 { 156 _locks.remove(documentId); 157 } 158 } 159 } 160 161 return cachePath; 162 } 163 164 /** 165 * Create images for a resource, in a specified folder. 166 * @param documentInputStream the input stream of the document 167 * @param documentName the name of the document 168 * @param baseFolder the base folder. 169 * @throws IOException if an error occurs while manipulating files 170 * @throws FlipbookException if an error occurs while manipulating the flipbook 171 */ 172 protected void createImages(InputStream documentInputStream, String documentName, File baseFolder) throws IOException, FlipbookException 173 { 174 // Create the document folder. 175 File documentFolder = new File(baseFolder, "document"); 176 documentFolder.mkdirs(); 177 178 // Create the document file. 179 File documentFile = new File(documentFolder, documentName); 180 181 try (FileOutputStream fos = new FileOutputStream(documentFile)) 182 { 183 IOUtils.copy(documentInputStream, fos); 184 fos.flush(); 185 } 186 187 // Trigger the images creation. 188 File imageFolder = new File(baseFolder, "pages"); 189 imageFolder.mkdirs(); 190 FileUtils.cleanDirectory(imageFolder); 191 192 _documentToImages.convert(documentFile, imageFolder); 193 } 194}