001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.loader; 029 030import java.io.InputStream; 031import java.io.OutputStream; 032import java.nio.file.Files; 033import java.nio.file.LinkOption; 034import java.nio.file.Path; 035import java.nio.file.Paths; 036import java.util.Comparator; 037import java.util.List; 038import java.util.stream.Collectors; 039import java.util.stream.Stream; 040 041/** 042 * RFS based storage for generated image cache entries.<p> 043 */ 044public class CmsRfsImageCache implements I_CmsImageCache { 045 046 /** The image cache repository. */ 047 private Path m_repository; 048 049 /** 050 * Creates a new RFS image cache.<p> 051 * 052 * @param repository the image cache repository path 053 * @throws Exception if the repository can not be initialized 054 */ 055 public CmsRfsImageCache(String repository) 056 throws Exception { 057 058 initRepository(Paths.get(repository).toAbsolutePath().normalize()); 059 } 060 061 /** 062 * @see org.opencms.loader.I_CmsImageCache#clear() 063 */ 064 @Override 065 public void clear() throws Exception { 066 067 Path repository = getRepository(); 068 List<Path> paths; 069 try (Stream<Path> stream = Files.walk(repository)) { 070 paths = stream.filter(path -> !repository.equals(path)).sorted(Comparator.reverseOrder()).collect( 071 Collectors.toList()); 072 } 073 for (Path path : paths) { 074 Files.deleteIfExists(path); 075 } 076 } 077 078 /** 079 * @see org.opencms.loader.I_CmsImageCache#exists(java.lang.String) 080 */ 081 public boolean exists(String key) throws Exception { 082 083 return Files.isRegularFile(getPath(key), LinkOption.NOFOLLOW_LINKS); 084 } 085 086 /** 087 * @see org.opencms.loader.I_CmsImageCache#getLength(java.lang.String) 088 */ 089 public long getLength(String key) throws Exception { 090 091 return Files.size(getPath(key)); 092 } 093 094 /** 095 * Returns the absolute repository path.<p> 096 * 097 * @return the repository path 098 */ 099 public String getRepositoryPath() { 100 101 if (m_repository == null) { 102 throw new IllegalStateException("Image cache has not been initialized."); 103 } 104 return m_repository.toString(); 105 } 106 107 /** 108 * @see org.opencms.loader.I_CmsImageCache#supportsRangeDelivery() 109 */ 110 public boolean supportsRangeDelivery() { 111 112 return true; 113 } 114 115 /** 116 * @see org.opencms.loader.I_CmsImageCache#visitEntries(org.opencms.loader.I_CmsImageCache.I_CmsImageCacheEntryVisitor) 117 */ 118 @Override 119 public void visitEntries(I_CmsImageCacheEntryVisitor visitor) throws Exception { 120 121 Path repository = getRepository(); 122 try (Stream<Path> paths = Files.walk(repository)) { 123 java.util.Iterator<Path> iterator = paths.filter( 124 candidate -> Files.isRegularFile(candidate, LinkOption.NOFOLLOW_LINKS)).iterator(); 125 while (iterator.hasNext()) { 126 Path path = iterator.next(); 127 String key = repository.relativize(path).toString().replace(path.getFileSystem().getSeparator(), "/"); 128 visitor.visit(key, Files.size(path)); 129 } 130 } 131 } 132 133 /** 134 * @see org.opencms.loader.I_CmsImageCache#write(java.lang.String, byte[]) 135 */ 136 public void write(String key, byte[] content) throws Exception { 137 138 Path path = getPath(key); 139 Files.createDirectories(path.getParent()); 140 Files.write(path, content); 141 } 142 143 /** 144 * @see org.opencms.loader.I_CmsImageCache#writeRangeTo(java.lang.String, long, long, java.io.OutputStream) 145 */ 146 public void writeRangeTo(String key, long start, long length, OutputStream out) throws Exception { 147 148 try (InputStream in = Files.newInputStream(getPath(key))) { 149 skipFully(in, start); 150 byte[] buffer = new byte[8192]; 151 long remaining = length; 152 while (remaining > 0) { 153 int read = in.read(buffer, 0, (int)Math.min(buffer.length, remaining)); 154 if (read < 0) { 155 return; 156 } 157 out.write(buffer, 0, read); 158 remaining -= read; 159 } 160 } 161 } 162 163 /** 164 * @see org.opencms.loader.I_CmsImageCache#writeTo(java.lang.String, java.io.OutputStream) 165 */ 166 public void writeTo(String key, OutputStream out) throws Exception { 167 168 Files.copy(getPath(key), out); 169 } 170 171 /** 172 * Returns the image cache repository.<p> 173 * 174 * @return the repository 175 */ 176 protected Path getRepository() { 177 178 if (m_repository == null) { 179 throw new IllegalStateException("Image cache has not been initialized."); 180 } 181 return m_repository; 182 } 183 184 /** 185 * Initializes the image cache repository.<p> 186 * 187 * @param repository the image cache repository 188 * 189 * @throws Exception if the repository can not be initialized 190 */ 191 protected void initRepository(Path repository) throws Exception { 192 193 m_repository = repository; 194 Files.createDirectories(m_repository); 195 if (!Files.isDirectory(m_repository, LinkOption.NOFOLLOW_LINKS)) { 196 throw new IllegalArgumentException("Image cache path is not a directory: " + m_repository); 197 } 198 } 199 200 /** 201 * Returns the cache path for a key.<p> 202 * 203 * @param key the image cache key 204 * @return the cache path 205 */ 206 private Path getPath(String key) { 207 208 if (m_repository == null) { 209 throw new IllegalStateException("Image cache has not been initialized."); 210 } 211 String normalizedKey = key; 212 while (normalizedKey.startsWith("/")) { 213 normalizedKey = normalizedKey.substring(1); 214 } 215 Path result = m_repository.resolve(normalizedKey).normalize(); 216 if (!result.startsWith(m_repository)) { 217 throw new IllegalArgumentException("Image cache key outside repository: " + key); 218 } 219 return result; 220 } 221 222 /** 223 * Skips exactly the requested number of bytes.<p> 224 * 225 * @param in the input stream 226 * @param bytes the number of bytes to skip 227 * @throws Exception if skipping fails 228 */ 229 private void skipFully(InputStream in, long bytes) throws Exception { 230 231 long remaining = bytes; 232 while (remaining > 0) { 233 long skipped = in.skip(remaining); 234 if (skipped > 0) { 235 remaining -= skipped; 236 } else if (in.read() < 0) { 237 return; 238 } else { 239 remaining--; 240 } 241 } 242 } 243}