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 org.opencms.configuration.CmsConfigurationException; 031import org.opencms.configuration.CmsParameterConfiguration; 032import org.opencms.db.storage.CmsFsStorage; 033import org.opencms.db.storage.CmsS3Storage; 034import org.opencms.db.storage.CmsStorageManager; 035import org.opencms.db.storage.s3.CmsS3ClientConfiguration; 036import org.opencms.util.CmsStringUtil; 037 038import java.net.URI; 039import java.nio.file.Path; 040import java.nio.file.Paths; 041 042/** 043 * Creates the external image cache selected in {@code opencms.properties}.<p> 044 */ 045public final class CmsImageCacheFactory { 046 047 /** Prefix for the direct image cache configuration. */ 048 public static final String PARAM_IMAGE_CACHE_PREFIX = "storage.imagecache."; 049 050 /** Property selecting the image cache type. */ 051 public static final String PARAM_IMAGE_CACHE_TYPE = PARAM_IMAGE_CACHE_PREFIX + "type"; 052 053 /** Hidden constructor. */ 054 private CmsImageCacheFactory() { 055 056 // utility class 057 } 058 059 /** 060 * Creates the configured external image cache.<p> 061 * 062 * @param configuration the runtime property configuration 063 * @return the configured cache, or <code>null</code> for the classic RFS image cache 064 * @throws CmsConfigurationException if the configuration is invalid or the cache is unavailable 065 */ 066 public static I_CmsImageCache create(CmsParameterConfiguration configuration) throws CmsConfigurationException { 067 068 validateConfiguration(configuration); 069 String type = getType(configuration); 070 try { 071 if (CmsS3Storage.STORAGE_TYPE.equals(type)) { 072 return new CmsS3ImageCache( 073 CmsStorageManager.createS3ClientConfiguration(configuration, PARAM_IMAGE_CACHE_PREFIX)); 074 } 075 if (CmsFsStorage.STORAGE_TYPE.equals(type)) { 076 return new CmsFsImageCache(getFileSystemPath(configuration).toString()); 077 } 078 return null; 079 } catch (Exception e) { 080 throw new CmsConfigurationException(Messages.get().container(Messages.ERR_IMAGE_CACHE_INIT_1, type), e); 081 } 082 } 083 084 /** 085 * Validates the image cache settings and separation from active and legacy data storage.<p> 086 * 087 * This does not open a cache or access S3, so offline maintenance tools can use the same validation. 088 * 089 * @param configuration the runtime property configuration 090 * @throws CmsConfigurationException if the configuration is invalid 091 */ 092 public static void validateConfiguration(CmsParameterConfiguration configuration) throws CmsConfigurationException { 093 094 String type = getType(configuration); 095 if (type == null) { 096 return; 097 } 098 try { 099 if (CmsS3Storage.STORAGE_TYPE.equals(type)) { 100 validateS3Configuration(configuration); 101 } else if (CmsFsStorage.STORAGE_TYPE.equals(type)) { 102 validateFileSystemConfiguration(configuration); 103 } else { 104 throw new CmsConfigurationException( 105 Messages.get().container(Messages.ERR_IMAGE_CACHE_CONFIG_TYPE_2, type, PARAM_IMAGE_CACHE_TYPE)); 106 } 107 } catch (CmsConfigurationException e) { 108 throw e; 109 } catch (Exception e) { 110 throw new CmsConfigurationException(Messages.get().container(Messages.ERR_IMAGE_CACHE_INIT_1, type), e); 111 } 112 } 113 114 /** Returns the required image cache root path. */ 115 private static Path getFileSystemPath(CmsParameterConfiguration configuration) { 116 117 String path = configuration.getString(PARAM_IMAGE_CACHE_PREFIX + "path", null); 118 if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) { 119 throw new IllegalArgumentException("Missing required configuration property: storage.imagecache.path"); 120 } 121 return normalizePath(path.trim()); 122 } 123 124 /** Returns the external cache type, or null for the classic local cache. */ 125 private static String getType(CmsParameterConfiguration configuration) { 126 127 String type = configuration.getString(PARAM_IMAGE_CACHE_TYPE, null); 128 return CmsStringUtil.isEmptyOrWhitespaceOnly(type) ? null : type.trim(); 129 } 130 131 /** Normalizes an S3 endpoint for comparisons. */ 132 private static String normalizeEndpoint(String endpoint) { 133 134 String result = URI.create(endpoint.trim()).normalize().toString(); 135 while (result.endsWith("/")) { 136 result = result.substring(0, result.length() - 1); 137 } 138 return result.toLowerCase(java.util.Locale.ROOT); 139 } 140 141 /** Normalizes a file system path. */ 142 private static Path normalizePath(String path) { 143 144 return Paths.get(path).toAbsolutePath().normalize(); 145 } 146 147 /** Ensures the FS cache does not overlap any data storage repository. */ 148 private static void validateFileSystemConfiguration(CmsParameterConfiguration configuration) 149 throws CmsConfigurationException { 150 151 Path imageCachePath = getFileSystemPath(configuration); 152 for (String dataStorageId : CmsStorageManager.getDataStorageIds(configuration)) { 153 if (!CmsFsStorage.STORAGE_TYPE.equals(CmsStorageManager.getStorageType(configuration, dataStorageId))) { 154 continue; 155 } 156 Path dataStoragePath = normalizePath( 157 CmsStorageManager.getFileSystemStoragePath(configuration, dataStorageId)); 158 if (imageCachePath.startsWith(dataStoragePath) || dataStoragePath.startsWith(imageCachePath)) { 159 throw new CmsConfigurationException( 160 Messages.get().container( 161 Messages.ERR_IMAGE_CACHE_CONFIG_FS_PATH_2, 162 imageCachePath, 163 dataStoragePath)); 164 } 165 } 166 } 167 168 /** Ensures the S3 cache has its own bucket, separate from all data storage buckets. */ 169 private static void validateS3Configuration(CmsParameterConfiguration configuration) 170 throws CmsConfigurationException { 171 172 CmsS3ClientConfiguration imageCache = CmsStorageManager.createS3ClientConfiguration( 173 configuration, 174 PARAM_IMAGE_CACHE_PREFIX); 175 for (String dataStorageId : CmsStorageManager.getDataStorageIds(configuration)) { 176 if (!CmsS3Storage.STORAGE_TYPE.equals(CmsStorageManager.getStorageType(configuration, dataStorageId))) { 177 continue; 178 } 179 CmsS3ClientConfiguration data = CmsStorageManager.createS3ClientConfiguration( 180 configuration, 181 dataStorageId, 182 null); 183 if (normalizeEndpoint(imageCache.getEndpoint()).equals(normalizeEndpoint(data.getEndpoint())) 184 && imageCache.getBucketName().equals(data.getBucketName())) { 185 throw new CmsConfigurationException( 186 Messages.get().container(Messages.ERR_IMAGE_CACHE_CONFIG_S3_BUCKET_1, imageCache.getBucketName())); 187 } 188 } 189 } 190}