Java tutorial
/** * Copyright (c) 2016 Henrik Bjerne * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions:The above copyright * notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ package bjerne.gallery.service.impl; import static org.apache.commons.io.FilenameUtils.*; import static org.apache.commons.io.FileUtils.*; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.io.comparator.NameFileComparator; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import bjerne.gallery.service.GalleryAuthorizationService; import bjerne.gallery.service.GalleryService; import bjerne.gallery.service.ImageResizeService; import bjerne.gallery.service.VideoConversionService; import bjerne.gallery.service.bean.GalleryFile; import bjerne.gallery.service.bean.GalleryFile.GalleryFileType; import bjerne.gallery.service.exception.NotAllowedException; /** * Implementation of the {@link GalleryService} interface. A log of services are * tied together via this class, such as services for authorization, resizing * and conversion. In addition, this class defines a number of file formats it * will accept. Any other formats will be disregarded even if the files are * allowed in terms of location. * * @author Henrik Bjerne * */ public class GalleryServiceImpl implements GalleryService { public static final String VIDEO_MODE_ORIGINAL = "ORIGINAL"; private final Logger LOG = LoggerFactory.getLogger(getClass()); private GalleryAuthorizationService galleryAuthorizationService; private ImageResizeService imageResizeService; private VideoConversionService videoConversionService; private File resizeDir; private Set<String> allowedFileExtensions; private final Comparator<String> directoryNameComparator = new CaseInsensitiveComparator(); private final IOFileFilter fileFilter = new CaseInsensitveFileEndingFilter(); private int maxImageWidth = 5000; private int maxImageHeight = 5000; @Required public void setGalleryAuthorizationService(GalleryAuthorizationService galleryAuthorizationService) { this.galleryAuthorizationService = galleryAuthorizationService; } @Required public void setImageResizeService(ImageResizeService imageResizeService) { this.imageResizeService = imageResizeService; } @Required public void setVideoConversionService(VideoConversionService videoConversionService) { this.videoConversionService = videoConversionService; } @Required public void setResizeDir(File resizeDir) { this.resizeDir = resizeDir; } public void setAllowedFileExtensions(Set<String> allowedFileExtensions) { this.allowedFileExtensions = allowedFileExtensions; } public void setMaxImageWidth(int maxImageWidth) { this.maxImageWidth = maxImageWidth; } public void setMaxImageHeight(int maxImageHeight) { this.maxImageHeight = maxImageHeight; } @Override public List<String> getRootDirectories() { List<String> rootDirCodes = new ArrayList<String>( galleryAuthorizationService.getRootPathsForCurrentUser().keySet()); Collections.sort(rootDirCodes, directoryNameComparator); return rootDirCodes; } @Override public GalleryFile getImage(String publicPath, int width, int height) throws IOException, NotAllowedException { LOG.debug("Entering getImage(publicPath={}, width={}, height={}", publicPath, width, height); if (width <= 0 && width > maxImageWidth || height <= 0 || height > maxImageHeight) { String errorMessage = String.format("Non valid image size requested. Width: %s, height: %s", width, height); LOG.error(errorMessage); throw new IOException(errorMessage); } File image = getRealFileOrDir(publicPath); if (isVideo(image)) { throw new IOException("Can only resize images!"); } File resizedImage = determineResizedImage(publicPath, width, height); LOG.debug("Resized filename: {}", resizedImage.getCanonicalPath()); if (!resizedImage.exists()) { LOG.debug("Resized file did not exist."); if (!image.exists()) { String errorMessage = String.format("Main image %s did not exist. Could not resize.", image.getCanonicalPath()); LOG.error(errorMessage); throw new FileNotFoundException(errorMessage); } imageResizeService.resizeImage(image, resizedImage, width, height); } return createGalleryFile(publicPath, resizedImage); } @Override public List<GalleryFile> getDirectoryListingFiles(String publicPath) throws IOException, NotAllowedException { File dir = getRealFileOrDir(publicPath); if (!dir.isDirectory()) { LOG.debug("File {} is not a directory. Returning null.", dir); return null; } Collection<File> fileCollection = (Collection<File>) listFiles(dir, fileFilter, null); List<File> fileList = new ArrayList<File>(fileCollection); Collections.sort(fileList, NameFileComparator.NAME_INSENSITIVE_COMPARATOR); LOG.debug("Found {} files for path {}", fileList.size(), publicPath); ArrayList<GalleryFile> galleryFiles = new ArrayList<GalleryFile>(fileList.size()); for (File oneFile : fileList) { galleryFiles.add(createGalleryFile(publicPath + '/' + oneFile.getName(), oneFile)); } return galleryFiles; } @Override public List<String> getDirectories(String publicPath) throws IOException, NotAllowedException { File dir = getRealFileOrDir(publicPath); List<String> directoryPaths = new ArrayList<>(); if (dir.isDirectory()) { for (File oneFile : dir.listFiles()) { if (oneFile.isDirectory()) { StringBuilder onePathBuilder = new StringBuilder(); onePathBuilder.append(publicPath); onePathBuilder.append(File.separator); onePathBuilder.append(oneFile.getName()); directoryPaths.add(separatorsToUnix(onePathBuilder.toString())); } } } LOG.debug("Found {} directories for path {}", directoryPaths.size(), publicPath); Collections.sort(directoryPaths, directoryNameComparator); return directoryPaths; } @Override public List<GalleryFile> getAllVideos() throws IOException, NotAllowedException { Map<String, File> rootPathsForCurrentUser = galleryAuthorizationService.getRootPathsForCurrentUser(); Map<String, Collection<File>> rootPathVideos = new HashMap<>(); for (Entry<String, File> oneEntry : rootPathsForCurrentUser.entrySet()) { List<File> videosForRootPath = listFiles(oneEntry.getValue(), fileFilter, FileFilterUtils.directoryFileFilter()).stream().filter(f -> isVideoNoException(f)) .collect(Collectors.toList()); rootPathVideos.put(oneEntry.getKey(), videosForRootPath); } List<GalleryFile> galleryFiles = new ArrayList<>(); for (Entry<String, Collection<File>> oneEntry : rootPathVideos.entrySet()) { for (File oneFile : oneEntry.getValue()) { galleryFiles.add(getGalleryFile(getPublicPathFromRealFile(oneEntry.getKey(), oneFile))); } } LOG.debug("Returning {} video files", galleryFiles.size()); return galleryFiles; } @Override public GalleryFile getGalleryFile(String publicPath) throws IOException, NotAllowedException { LOG.debug("Entering getGalleryFile(publicPath={})", publicPath); File file = getRealFileOrDir(publicPath); if (file.isDirectory()) { throw new FileNotFoundException("Directories not allowed in this method!"); } return createGalleryFile(publicPath, file); } @Override public List<String> getAvailableVideoModes() { List<String> availableVideoModes = new ArrayList<>(videoConversionService.getAvailableVideoModes()); availableVideoModes.add(VIDEO_MODE_ORIGINAL); return availableVideoModes; } @Override public GalleryFile getVideo(String publicPath, String videoMode) throws IOException, NotAllowedException { LOG.debug("Entering getVideo(publicPath={}, videoMode={}", publicPath, videoMode); if (StringUtils.isEmpty(videoMode) || !getAvailableVideoModes().contains(videoMode)) { throw new IOException("videoMode not defined!"); } File video = getRealFileOrDir(publicPath); File convertedVideo = null; if (VIDEO_MODE_ORIGINAL.equals(videoMode)) { LOG.debug("Video mode was {}. Will return original video.", VIDEO_MODE_ORIGINAL); convertedVideo = video; } else { convertedVideo = determineConvertedVideo(publicPath, videoMode); LOG.debug("Converted video filename: {}", convertedVideo); if (!convertedVideo.exists()) { LOG.debug("Resized file did not exist."); if (!video.exists()) { String errorMessage = String.format("Main video %s did not exist. Could not resize.", video.getCanonicalPath()); LOG.error(errorMessage); throw new FileNotFoundException(errorMessage); } videoConversionService.convertVideo(video, convertedVideo, videoMode); } } return createGalleryFile(publicPath, convertedVideo); } private File getRealFileOrDir(String publicPath) throws IOException, FileNotFoundException, NotAllowedException { LOG.debug("Entering getRealFileOrDir(publicPath={})", publicPath); if (StringUtils.isBlank(publicPath)) { throw new FileNotFoundException("Could not extract code from empty path!"); } int index = publicPath.indexOf("/"); if (index < 0) { index = publicPath.length(); } String baseDirCode = publicPath.substring(0, index); LOG.debug("baseDirCode: {}", baseDirCode); File baseDir = galleryAuthorizationService.getRootPathsForCurrentUser().get(baseDirCode); if (baseDir == null) { String errorMessage = String.format("Could not find basedir for base dir code {}", baseDirCode); LOG.error(errorMessage); throw new FileNotFoundException(errorMessage); } File file = null; String relativePath = publicPath.substring(index, publicPath.length()); LOG.debug("Relative path: {}", relativePath); if (StringUtils.isNotBlank(relativePath)) { file = new File(baseDir, relativePath); checkAllowed(baseDir, file); } else { // Don't need to check allowed on baseDir as this was just returned // from the authorization service. file = baseDir; } if (!file.exists()) { throw new FileNotFoundException("File not found!"); } if (!file.isDirectory() && !isAllowedExtension(file)) { throw new NotAllowedException("File " + publicPath + " did not have an allowed file extension"); } return file; } /** * A kind of inverse lookup - finding the public path given the actual file. * <strong>NOTE! This method does NOT verify that the current user actually * has the right to access the given rootPath! It is the responsibility of * calling methods to make sure only allowed root paths are used.</strong> * * @param rootPath * @param file * @return The public path of the given file for the given rootPath. * @throws IOException * @throws NotAllowedException */ private String getPublicPathFromRealFile(String rootPath, File file) throws IOException, NotAllowedException { String actualFilePath = file.getCanonicalPath(); File rootFile = galleryAuthorizationService.getRootPathsForCurrentUser().get(rootPath); String relativePath = actualFilePath.substring(rootFile.getCanonicalPath().length(), actualFilePath.length()); StringBuilder builder = new StringBuilder(); builder.append(rootPath); builder.append(relativePath); String publicPath = separatorsToUnix(builder.toString()); LOG.debug("Actual file: {}, generated public path: {}", file, publicPath); return publicPath; } private GalleryFile createGalleryFile(String publicPath, File actualFile) throws IOException { String contentType = getContentType(actualFile); GalleryFile galleryFile = new GalleryFile(); galleryFile.setPublicPath(publicPath); galleryFile.setActualFile(actualFile); galleryFile.setContentType(contentType); if (isVideo(actualFile)) { galleryFile.setType(GalleryFileType.VIDEO); } else { galleryFile.setType(GalleryFileType.IMAGE); } return galleryFile; } private String getContentType(File file) throws IOException { return Files.probeContentType(file.toPath()); } private boolean isVideoNoException(File file) { try { return isVideo(file); } catch (IOException ioe) { return false; } } private boolean isVideo(File file) throws IOException { return StringUtils.startsWith(getContentType(file), "video"); } private File determineResizedImage(String path, int width, int height) { String resizePart = Integer.valueOf(width).toString() + "x" + Integer.valueOf(height).toString(); File resizedImage = new File(resizeDir, File.separator + resizePart + File.separator + path); return resizedImage; } private File determineConvertedVideo(String path, String videoMode) { File resizedImage = new File(resizeDir, File.separator + videoMode + File.separator + path); return resizedImage; } private void checkAllowed(File baseDir, File fileToCheck) throws IOException, NotAllowedException { boolean allowed = galleryAuthorizationService.isAllowed(fileToCheck); if (!allowed) { throw new NotAllowedException("File " + fileToCheck + " not allowed!"); } } private boolean isAllowedExtension(File file) { return allowedFileExtensions.contains(getExtension(file.getName()).toLowerCase()); } private class CaseInsensitiveComparator implements Comparator<String> { @Override public int compare(String o1, String o2) { return o1.toLowerCase().compareTo(o2.toLowerCase()); } } private class CaseInsensitveFileEndingFilter implements IOFileFilter { @Override public boolean accept(File file) { return isAllowedExtension(file); } @Override public boolean accept(File dir, String name) { return false; } } }