001/* 002 * Copyright 2012 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 */ 016 017package org.ametys.plugins.core.impl.upload; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileNotFoundException; 022import java.io.FileOutputStream; 023import java.io.FilenameFilter; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.time.ZonedDateTime; 028import java.util.Collection; 029import java.util.NoSuchElementException; 030import java.util.UUID; 031 032import org.apache.avalon.framework.activity.Initializable; 033import org.apache.avalon.framework.context.Context; 034import org.apache.avalon.framework.context.ContextException; 035import org.apache.avalon.framework.context.Contextualizable; 036import org.apache.avalon.framework.logger.LogEnabled; 037import org.apache.avalon.framework.logger.Logger; 038import org.apache.avalon.framework.thread.ThreadSafe; 039import org.apache.cocoon.Constants; 040import org.apache.commons.io.FileUtils; 041import org.apache.commons.io.IOUtils; 042import org.apache.commons.io.filefilter.DirectoryFileFilter; 043import org.apache.commons.io.filefilter.FalseFileFilter; 044import org.apache.commons.io.filefilter.TrueFileFilter; 045 046import org.ametys.core.upload.Upload; 047import org.ametys.core.upload.UploadManager; 048import org.ametys.core.user.UserIdentity; 049import org.ametys.core.util.DateUtils; 050import org.ametys.core.util.FilenameUtils; 051import org.ametys.core.util.URIUtils; 052import org.ametys.runtime.util.AmetysHomeHelper; 053 054/** 055 * {@link UploadManager} which stores uploaded files into the 056 * <code>uploads-user</code> directory located in Ametys home 057 * <p> 058 * Note that this implementation is not cluster safe. 059 */ 060public class FSUploadManager implements UploadManager, ThreadSafe, Initializable, LogEnabled, Contextualizable 061{ 062 /** 063 * The path to the global uploads directory relative to ametys home 064 */ 065 public static final String UPLOADS_DIRECTORY = "uploads-user"; 066 067 /** Context. */ 068 protected org.apache.cocoon.environment.Context _cocoonContext; 069 /** Global uploads directory. */ 070 protected File _globalUploadsDir; 071 /** Logger available to subclasses. */ 072 private Logger _logger; 073 074 @Override 075 public void contextualize(Context context) throws ContextException 076 { 077 // Retrieve context-root from context 078 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 079 } 080 081 @Override 082 public void enableLogging(Logger logger) 083 { 084 _logger = logger; 085 } 086 087 @Override 088 public void initialize() throws Exception 089 { 090 _globalUploadsDir = new File(AmetysHomeHelper.getAmetysHomeData(), UPLOADS_DIRECTORY); 091 } 092 093 @Override 094 public Upload storeUpload(UserIdentity user, String filename, InputStream is) throws IOException 095 { 096 if (!_globalUploadsDir.exists()) 097 { 098 if (!_globalUploadsDir.mkdirs()) 099 { 100 throw new IOException("Unable to create directory: " + _globalUploadsDir); 101 } 102 } 103 104 // Create unique id 105 String id = UUID.randomUUID().toString(); 106 107 File uploadFile = null; 108 109 uploadFile = new File(_getUploadDir(user != null ? UserIdentity.userIdentityToString(user) : null, id), FilenameUtils.encodeName(filename)); 110 111 if (_logger.isInfoEnabled()) 112 { 113 _logger.info("Using file: " + uploadFile); 114 } 115 116 if (!uploadFile.getParentFile().mkdirs()) 117 { 118 throw new IOException("Unable to create directory: " + uploadFile.getParent()); 119 } 120 121 try (OutputStream os = new FileOutputStream(uploadFile)) 122 { 123 IOUtils.copy(is, os); 124 } 125 126 return new FSUpload(_cocoonContext, uploadFile); 127 } 128 129 @Override 130 public Upload getUpload(UserIdentity user, String id) throws NoSuchElementException 131 { 132 File uploadDir = _getUploadDir(user != null ? UserIdentity.userIdentityToString(user) : null, id); 133 134 if (_logger.isDebugEnabled()) 135 { 136 _logger.debug("Using directory: " + uploadDir); 137 } 138 139 if (!uploadDir.exists() || !uploadDir.isDirectory()) 140 { 141 throw new NoSuchElementException("No directory: " + uploadDir); 142 } 143 144 Collection<File> files = FileUtils.listFiles(uploadDir, TrueFileFilter.INSTANCE, FalseFileFilter.INSTANCE); 145 146 if (_logger.isInfoEnabled()) 147 { 148 _logger.info("Found files: " + files); 149 } 150 151 if (files.isEmpty()) 152 { 153 throw new NoSuchElementException("No files in directory: " + uploadDir); 154 } 155 156 // Use first file found 157 return new FSUpload(_cocoonContext, files.iterator().next()); 158 } 159 160 /** 161 * Retrieves the upload directory for a login and an upload id. 162 * @param login the login. 163 * @param id the upload id. 164 * @return the upload directory. 165 */ 166 protected File _getUploadDir(String login, String id) 167 { 168 String fileName = login != null ? FilenameUtils.encodeName(login) : "_Anonymous"; 169 return new File(new File(_globalUploadsDir, fileName), id); 170 } 171 172 /** 173 * {@link Upload} implementation on file system. 174 */ 175 protected static class FSUpload implements Upload 176 { 177 private org.apache.cocoon.environment.Context _context; 178 private File _file; 179 180 /** 181 * Creates a FSUpload from a file. 182 * @param context the context. 183 * @param file the file. 184 */ 185 public FSUpload(org.apache.cocoon.environment.Context context, File file) 186 { 187 _context = context; 188 _file = file; 189 } 190 191 @Override 192 public String getId() 193 { 194 return _file.getParentFile().getName(); 195 } 196 197 @Override 198 public ZonedDateTime getUploadedDate() 199 { 200 return DateUtils.asZonedDateTime(_file.lastModified()); 201 } 202 203 @Override 204 public String getFilename() 205 { 206 return URIUtils.decode(_file.getName()); 207 } 208 209 @Override 210 public String getMimeType() 211 { 212 String mimeType = _context.getMimeType(getFilename().toLowerCase()); 213 214 if (mimeType == null) 215 { 216 mimeType = "application/unknown"; 217 } 218 219 return mimeType; 220 } 221 222 @Override 223 public long getLength() 224 { 225 return _file.length(); 226 } 227 228 @Override 229 public InputStream getInputStream() 230 { 231 try 232 { 233 return new FileInputStream(_file); 234 } 235 catch (FileNotFoundException e) 236 { 237 throw new RuntimeException("Missing file: " + _file, e); 238 } 239 } 240 } 241 242 /** 243 * get directories under upload-user directory 244 * @return the directories 245 */ 246 public File[] getUploadsDirectories() 247 { 248 return _globalUploadsDir.listFiles((FilenameFilter) DirectoryFileFilter.INSTANCE); 249 } 250}