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.ui.apps.cacheadmin; 029 030import com.alkacon.simapi.Simapi; 031 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProject; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.loader.CmsImageLoader; 037import org.opencms.loader.I_CmsImageCache; 038import org.opencms.main.CmsException; 039import org.opencms.main.OpenCms; 040import org.opencms.util.CmsFileUtil; 041import org.opencms.util.CmsStringUtil; 042 043import java.awt.image.BufferedImage; 044import java.io.ByteArrayOutputStream; 045import java.io.File; 046import java.io.IOException; 047import java.util.ArrayList; 048import java.util.Collections; 049import java.util.HashMap; 050import java.util.List; 051import java.util.Map; 052 053/** 054 * Image Cache helper.<p> 055 * 056 * @since 7.0.5 057 */ 058public class CmsImageCacheHelper { 059 060 /**File path map. */ 061 private Map<String, String> m_filePaths = new HashMap<String, String>(); 062 063 /** Lengths map. */ 064 private Map m_lengths = new HashMap(); 065 066 /** Sizes map. */ 067 private Map m_sizes = new HashMap(); 068 069 /** Variations map. */ 070 private Map m_variations = new HashMap(); 071 072 /** The total number of variations. */ 073 private int m_variationsCount; 074 075 /** The total size of all variations. */ 076 private int m_variationsSize; 077 078 /** 079 * Default constructor.<p> 080 * 081 * @param cms the cms context 082 * @param withVariations if also variations should be read 083 * @param showSize if it is needed to compute the image size 084 * @param statsOnly if only statistical information should be retrieved 085 */ 086 public CmsImageCacheHelper(CmsObject cms, boolean withVariations, boolean showSize, boolean statsOnly) { 087 088 init(cms, withVariations, showSize, statsOnly); 089 } 090 091 /** 092 * Returns all cached images.<p> 093 * 094 * @return a list of root paths 095 */ 096 public List getAllCachedImages() { 097 098 List ret = new ArrayList(m_variations.keySet()); 099 Collections.sort(ret); 100 return ret; 101 } 102 103 /** 104 * Returns the total number of files.<p> 105 * 106 * @return the total number of files 107 */ 108 public int getFilesCount() { 109 110 return m_variations.keySet().size(); 111 } 112 113 /** 114 * Returns the length of the given image.<p> 115 * 116 * @param imgName the image name 117 * 118 * @return the length of the given image 119 */ 120 public String getLength(String imgName) { 121 122 String ret = (String)m_lengths.get(imgName); 123 if (ret == null) { 124 return ""; 125 } 126 return ret; 127 } 128 129 /** 130 * Reads the size of a single image.<p> 131 * 132 * @param cms CmsObejct 133 * @param resPath Path of image (uri) 134 * @return a String representation of the dimension of the given image 135 * @throws CmsException if something goes wrong 136 */ 137 public String getSingleSize(CmsObject cms, String resPath) throws CmsException { 138 139 CmsResource res = getClonedCmsObject(cms).readResource(resPath); 140 return getSingleSize(cms, res); 141 } 142 143 /** 144 * Returns the size of the given image.<p> 145 * 146 * @param imgName the image name 147 * 148 * @return the size of the given image 149 */ 150 public String getSize(String imgName) { 151 152 String ret = (String)m_sizes.get(imgName); 153 if (ret == null) { 154 return ""; 155 } 156 return ret; 157 } 158 159 /** 160 * Returns the variations for the given image.<p> 161 * 162 * @param imgName the image name 163 * 164 * @return the variations for the given image 165 */ 166 public List getVariations(String imgName) { 167 168 List ret = (List)m_variations.get(imgName); 169 if (ret == null) { 170 return new ArrayList(); 171 } 172 Collections.sort(ret); 173 return ret; 174 } 175 176 /** 177 * Returns the total number of variations.<p> 178 * 179 * @return the total number of variations 180 */ 181 public int getVariationsCount() { 182 183 return m_variationsCount; 184 } 185 186 /** 187 * Returns the total size of all variations.<p> 188 * 189 * @return the total size of all variations 190 */ 191 public int getVariationsSize() { 192 193 return m_variationsSize; 194 } 195 196 /** 197 * Reads out the path of a given image (by its name).<p> 198 * 199 * @param resName name of image 200 * @return path to it on Server 201 */ 202 String getFilePath(String resName) { 203 204 return m_filePaths.get(resName); 205 } 206 207 /** 208 * Finds the original VFS image resource for a cache entry name.<p> 209 * 210 * @param cms the CMS context 211 * @param imageName the image cache entry name 212 * @return the original resource, or <code>null</code> 213 */ 214 private CmsResource findOriginalResource(CmsObject cms, String imageName) { 215 216 String candidate = imageName; 217 boolean found = false; 218 while (!found) { 219 String path = CmsResource.getParentFolder(candidate); 220 String name = candidate.substring(path.length()); 221 String ext = CmsFileUtil.getExtension(candidate); 222 String nameWoExt = name.substring(0, name.length() - ext.length()); 223 int pos = nameWoExt.lastIndexOf("_"); 224 String newName = path; 225 found = (pos < 0); 226 if (!found) { 227 newName += nameWoExt.substring(0, pos); 228 } else { 229 newName += nameWoExt; 230 } 231 newName += ext; 232 try { 233 return cms.readResource(newName, CmsResourceFilter.ALL); 234 } catch (Exception e) { 235 // it could be a variation 236 } 237 candidate = newName; 238 } 239 return null; 240 } 241 242 /** 243 * Clones a CmsObject.<p> 244 * 245 * @param cms the CmsObject to be cloned. 246 * @return a clones CmsObject 247 * @throws CmsException if something goes wrong 248 */ 249 private CmsObject getClonedCmsObject(CmsObject cms) throws CmsException { 250 251 CmsObject clonedCms = OpenCms.initCmsObject(cms); 252 // only online images get caches 253 clonedCms.getRequestContext().setCurrentProject(clonedCms.readProject(CmsProject.ONLINE_PROJECT_ID)); 254 // paths are always root path 255 clonedCms.getRequestContext().setSiteRoot(""); 256 257 return clonedCms; 258 } 259 260 /** 261 * Reads image dimensions from an external image cache entry.<p> 262 * 263 * @param imageCache the external image cache 264 * @param key the image cache key 265 * @return the formatted image dimensions, or an empty string 266 */ 267 private String getImageDimensions(I_CmsImageCache imageCache, String key) { 268 269 try { 270 ByteArrayOutputStream out = new ByteArrayOutputStream(); 271 imageCache.writeTo(key, out); 272 BufferedImage image = Simapi.read(out.toByteArray()); 273 return "" + image.getWidth() + " x " + image.getHeight() + "px"; 274 } catch (Exception e) { 275 return ""; 276 } 277 } 278 279 /** 280 * Reads the size of a single image.<p> 281 * 282 * @param cms CmsObejct 283 * @param res CmsResource to be read 284 * @return a String representation of the dimension of the given image 285 */ 286 private String getSingleSize(CmsObject cms, CmsResource res) { 287 288 try { 289 BufferedImage img = Simapi.read(cms.readFile(res).getContents()); 290 return "" + img.getWidth() + " x " + img.getHeight() + "px"; 291 } catch (CmsException | IOException e) { 292 return ""; 293 } 294 } 295 296 /** 297 * Reads all cached images.<p> 298 * 299 * @param cms the cms context 300 * @param withVariations if also variations should be read 301 * @param showSize if it is needed to compute the image size 302 * @param statsOnly if only statistical information should be retrieved 303 */ 304 private void init(CmsObject cms, boolean withVariations, boolean showSize, boolean statsOnly) { 305 306 String repositoryPath = CmsImageLoader.getImageRepositoryPath(); 307 try { 308 CmsObject clonedCms = getClonedCmsObject(cms); 309 I_CmsImageCache imageCache = CmsImageLoader.getImageCache(); 310 if (imageCache != null) { 311 imageCache.visitEntries( 312 ( 313 key, 314 length) -> visitImage(clonedCms, imageCache, key, length, withVariations, showSize, statsOnly)); 315 } else if (repositoryPath != null) { 316 visitImages(clonedCms, new File(repositoryPath), withVariations, showSize, statsOnly); 317 } 318 } catch (Exception e) { 319 // should never happen 320 } 321 m_variations = Collections.unmodifiableMap(m_variations); 322 m_sizes = Collections.unmodifiableMap(m_sizes); 323 m_lengths = Collections.unmodifiableMap(m_lengths); 324 } 325 326 /** 327 * Visits a single image.<p> 328 * 329 * @param cms CmsObject 330 * @param f a File to be read out 331 * @param withVariations boolean 332 * @param showSize boolean 333 * @param statsOnly boolean 334 */ 335 private void visitImage(CmsObject cms, File f, boolean withVariations, boolean showSize, boolean statsOnly) { 336 337 m_variationsCount++; 338 m_variationsSize += f.length(); 339 String oName = f.getAbsolutePath().substring(CmsImageLoader.getImageRepositoryPath().length()); 340 oName = CmsStringUtil.substitute(oName, "\\", "/"); 341 if (!oName.startsWith("/")) { 342 oName = "/" + oName; 343 } 344 CmsResource res = findOriginalResource(cms, oName); 345 346 if (res != null) { 347 oName = res.getRootPath(); 348 } 349 m_filePaths.put(oName, f.getAbsolutePath()); 350 List variations = (List)m_variations.get(oName); 351 if (variations == null) { 352 variations = new ArrayList(); 353 m_variations.put(oName, variations); 354 if (statsOnly) { 355 return; 356 } 357 if (res != null) { 358 m_lengths.put(oName, "" + res.getLength() + " Bytes"); 359 if (showSize) { 360 m_sizes.put(oName, getSingleSize(cms, res)); 361 } 362 } else { 363 m_lengths.put(oName, "" + f.length() + " Bytes"); 364 if (showSize) { 365 try { 366 BufferedImage img = Simapi.read(f); 367 m_sizes.put(oName, "" + img.getWidth() + " x " + img.getHeight() + "px"); 368 } catch (Throwable e) { 369 // ignore 370 } 371 } 372 } 373 } 374 if (!withVariations) { 375 return; 376 } 377 oName += " ("; 378 if (showSize) { 379 try { 380 BufferedImage img = Simapi.read(f); 381 oName += "" + img.getWidth() + " x " + img.getHeight() + "px - "; 382 } catch (Throwable e) { 383 // ignore 384 } 385 } 386 oName += f.length() + " Bytes)"; 387 variations.add(oName); 388 } 389 390 /** 391 * Visits a single image in an external image cache.<p> 392 * 393 * @param cms the CMS context 394 * @param imageCache the external image cache 395 * @param key the image cache key 396 * @param length the image cache entry length 397 * @param withVariations whether variations should be collected 398 * @param showSize whether image dimensions should be read 399 * @param statsOnly whether only statistics should be collected 400 */ 401 private void visitImage( 402 CmsObject cms, 403 I_CmsImageCache imageCache, 404 String key, 405 long length, 406 boolean withVariations, 407 boolean showSize, 408 boolean statsOnly) { 409 410 m_variationsCount++; 411 m_variationsSize += length; 412 String cacheName = CmsStringUtil.substitute(key, "\\", "/"); 413 if (!cacheName.startsWith("/")) { 414 cacheName = "/" + cacheName; 415 } 416 CmsResource res = findOriginalResource(cms, cacheName); 417 String imageName = (res != null) ? res.getRootPath() : cacheName; 418 m_filePaths.put(imageName, key); 419 List variations = (List)m_variations.get(imageName); 420 if (variations == null) { 421 variations = new ArrayList(); 422 m_variations.put(imageName, variations); 423 if (statsOnly) { 424 return; 425 } 426 if (res != null) { 427 m_lengths.put(imageName, "" + res.getLength() + " Bytes"); 428 if (showSize) { 429 m_sizes.put(imageName, getSingleSize(cms, res)); 430 } 431 } else { 432 m_lengths.put(imageName, "" + length + " Bytes"); 433 if (showSize) { 434 m_sizes.put(imageName, getImageDimensions(imageCache, key)); 435 } 436 } 437 } 438 if (!withVariations) { 439 return; 440 } 441 StringBuilder variation = new StringBuilder(imageName).append(" ("); 442 if (showSize) { 443 String dimensions = getImageDimensions(imageCache, key); 444 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(dimensions)) { 445 variation.append(dimensions).append(" - "); 446 } 447 } 448 variation.append(length).append(" Bytes)"); 449 variations.add(variation.toString()); 450 } 451 452 /** 453 * Visits all cached images in the given directory.<p> 454 * 455 * @param cms the cms context 456 * @param directory the directory to visit 457 * @param withVariations if also variations should be read 458 * @param showSize if it is needed to compute the image size 459 * @param statsOnly if only statistical information should be retrieved 460 */ 461 private void visitImages( 462 CmsObject cms, 463 File directory, 464 boolean withVariations, 465 boolean showSize, 466 boolean statsOnly) { 467 468 if (!directory.canRead() || !directory.isDirectory()) { 469 return; 470 } 471 File[] files = directory.listFiles(); 472 for (int i = 0; i < files.length; i++) { 473 File f = files[i]; 474 if (f.isDirectory()) { 475 visitImages(cms, f, withVariations, showSize, statsOnly); 476 continue; 477 } 478 visitImage(cms, f, withVariations, showSize, statsOnly); 479 } 480 } 481 482}