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, 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.xml.containerpage; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsPropertyDefinition; 033import org.opencms.file.CmsResource; 034import org.opencms.file.history.I_CmsHistoryResource; 035import org.opencms.i18n.CmsEncoder; 036import org.opencms.loader.CmsLoaderException; 037import org.opencms.main.CmsException; 038import org.opencms.main.OpenCms; 039import org.opencms.xml.CmsXmlContentDefinition; 040import org.opencms.xml.CmsXmlEntityResolver; 041import org.opencms.xml.CmsXmlException; 042import org.opencms.xml.CmsXmlUtils; 043import org.opencms.xml.content.Messages; 044 045import java.io.UnsupportedEncodingException; 046import java.util.Locale; 047 048import org.dom4j.Document; 049import org.dom4j.DocumentHelper; 050import org.xml.sax.EntityResolver; 051 052import jakarta.servlet.ServletRequest; 053 054/** 055 * Provides factory methods to unmarshal (read) an group container object.<p> 056 * 057 * @since 8.0.0 058 */ 059public final class CmsXmlGroupContainerFactory { 060 061 /** 062 * No instances of this class should be created.<p> 063 */ 064 private CmsXmlGroupContainerFactory() { 065 066 // noop 067 } 068 069 /** 070 * Create a new instance of an group container based on the given default content, 071 * that will have all language nodes of the default content and ensures the presence of the given locale.<p> 072 * 073 * The given encoding is used when marshalling the XML again later.<p> 074 * 075 * @param cms the current users OpenCms content 076 * @param locale the locale to generate the default content for 077 * @param modelUri the absolute path to the group container file acting as model 078 * 079 * @throws CmsException in case the model file is not found or not valid 080 * 081 * @return the created group container 082 */ 083 public static CmsXmlGroupContainer createDocument(CmsObject cms, Locale locale, String modelUri) 084 throws CmsException { 085 086 // create the XML content 087 CmsXmlGroupContainer content = new CmsXmlGroupContainer(cms, locale, modelUri); 088 // call prepare for use content handler and return the result 089 return (CmsXmlGroupContainer)content.getHandler().prepareForUse(cms, content); 090 } 091 092 /** 093 * Create a new instance of a group container based on the given content definition, 094 * that will have one language node for the given locale all initialized with default values.<p> 095 * 096 * The given encoding is used when marshalling the XML again later.<p> 097 * 098 * @param cms the current users OpenCms content 099 * @param locale the locale to generate the default content for 100 * @param encoding the encoding to use when marshalling the XML content later 101 * @param contentDefinition the content definition to create the content for 102 * 103 * @return the created group container 104 */ 105 public static CmsXmlGroupContainer createDocument( 106 CmsObject cms, 107 Locale locale, 108 String encoding, 109 CmsXmlContentDefinition contentDefinition) { 110 111 // create the XML content 112 CmsXmlGroupContainer content = new CmsXmlGroupContainer(cms, locale, encoding, contentDefinition); 113 // call prepare for use content handler and return the result 114 return (CmsXmlGroupContainer)content.getHandler().prepareForUse(cms, content); 115 } 116 117 /** 118 * Factory method to unmarshal (generate) a group container instance from a byte array 119 * that contains XML data.<p> 120 * 121 * When unmarshalling, the encoding is read directly from the XML header of the byte array. 122 * The given encoding is used only when marshalling the XML again later.<p> 123 * 124 * <b>Warning:</b><br/> 125 * This method does not support requested historic versions, it always loads the 126 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 127 * for history support.<p> 128 * 129 * @param cms the cms context 130 * @param xmlData the XML data in a byte array 131 * @param encoding the encoding to use when marshalling the XML content later 132 * @param resolver the XML entitiy resolver to use 133 * 134 * @return a group container instance unmarshalled from the byte array 135 * 136 * @throws CmsXmlException if something goes wrong 137 */ 138 public static CmsXmlGroupContainer unmarshal( 139 CmsObject cms, 140 byte[] xmlData, 141 String encoding, 142 EntityResolver resolver) 143 throws CmsXmlException { 144 145 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 146 } 147 148 /** 149 * Factory method to unmarshal (read) a group container instance from a OpenCms VFS file 150 * that contains XML data.<p> 151 * 152 * <b>Warning:</b><br/> 153 * This method does not support requested historic versions, it always loads the 154 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 155 * for history support.<p> 156 * 157 * @param cms the current cms object 158 * @param file the file with the XML data to unmarshal 159 * 160 * @return a group container instance unmarshalled from the provided file 161 * 162 * @throws CmsXmlException if something goes wrong 163 */ 164 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsFile file) throws CmsXmlException { 165 166 return unmarshal(cms, file, true); 167 } 168 169 /** 170 * Factory method to unmarshal (read) a group container instance from a OpenCms VFS file 171 * that contains XML data, using wither the encoding set 172 * in the XML file header, or the encoding set in the VFS file property.<p> 173 * 174 * If you are not sure about the implications of the encoding issues, 175 * use {@link #unmarshal(CmsObject, CmsFile)} instead.<p> 176 * 177 * <b>Warning:</b><br/> 178 * This method does not support requested historic versions, it always loads the 179 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 180 * for history support.<p> 181 * 182 * @param cms the current cms object 183 * @param file the file with the XML data to unmarshal 184 * @param keepEncoding if <code>true</code>, the encoding specified in the XML header is used, 185 * otherwise the encoding from the VFS file property is used 186 * 187 * @return a group container instance unmarshalled from the provided file 188 * 189 * @throws CmsXmlException if something goes wrong 190 */ 191 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsFile file, boolean keepEncoding) 192 throws CmsXmlException { 193 194 // check the cache 195 CmsXmlGroupContainer content = getCache(cms, file, keepEncoding); 196 if (content != null) { 197 return content; 198 } 199 200 // not found in cache, read as normally 201 byte[] contentBytes = file.getContents(); 202 String filename = cms.getSitePath(file); 203 204 String encoding = null; 205 try { 206 encoding = cms.readPropertyObject( 207 filename, 208 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 209 true).getValue(); 210 } catch (CmsException e) { 211 // encoding will be null 212 } 213 if (encoding == null) { 214 encoding = OpenCms.getSystemInfo().getDefaultEncoding(); 215 } else { 216 encoding = CmsEncoder.lookupEncoding(encoding, null); 217 if (encoding == null) { 218 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, filename)); 219 } 220 } 221 222 if (contentBytes.length > 0) { 223 // content is initialized 224 if (keepEncoding) { 225 // use the encoding from the content 226 content = unmarshal(cms, contentBytes, encoding, new CmsXmlEntityResolver(cms)); 227 } else { 228 // use the encoding from the file property 229 // this usually only triggered by a save operation 230 try { 231 String contentStr = new String(contentBytes, encoding); 232 content = unmarshal(cms, contentStr, encoding, new CmsXmlEntityResolver(cms)); 233 } catch (UnsupportedEncodingException e) { 234 // this will not happen since the encoding has already been validated 235 throw new CmsXmlException( 236 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, filename)); 237 } 238 } 239 } else { 240 // content is empty 241 content = new CmsXmlGroupContainer( 242 cms, 243 DocumentHelper.createDocument(), 244 encoding, 245 new CmsXmlEntityResolver(cms)); 246 } 247 248 // set the file 249 content.setFile(file); 250 // call prepare for use content handler and return the result 251 CmsXmlGroupContainer xmlGroupContainer = (CmsXmlGroupContainer)content.getHandler().prepareForUse(cms, content); 252 253 // set the cache 254 setCache(cms, xmlGroupContainer, keepEncoding); 255 256 return xmlGroupContainer; 257 } 258 259 /** 260 * Factory method to unmarshal (read) a group container instance from a OpenCms VFS resource 261 * that contains XML data.<p> 262 * 263 * <b>Warning:</b><br/> 264 * This method does not support requested historic versions, it always loads the 265 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 266 * for history support.<p> 267 * 268 * @param cms the current cms object 269 * @param resource the resource with the XML data to unmarshal 270 * 271 * @return a group container instance unmarshalled from the provided resource 272 * 273 * @throws CmsException if something goes wrong 274 */ 275 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsResource resource) throws CmsException { 276 277 // check the cache 278 CmsXmlGroupContainer content = getCache(cms, resource, true); 279 if (content != null) { 280 return content; 281 } 282 283 content = unmarshal(cms, cms.readFile(resource), true); 284 285 // set the cache 286 setCache(cms, content, true); 287 288 return content; 289 } 290 291 /** 292 * Factory method to unmarshal (read) a group container instance from 293 * a resource, using the request attributes as cache.<p> 294 * 295 * @param cms the current OpenCms context object 296 * @param resource the resource to unmarshal 297 * @param req the current request 298 * 299 * @return the unmarshaled xml content 300 * 301 * @throws CmsException in something goes wrong 302 * @throws CmsLoaderException if no loader for the given <code>resource</code> type ({@link CmsResource#getTypeId()}) is available 303 * @throws CmsXmlException if the given <code>resource</code> is not of type group container 304 */ 305 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsResource resource, ServletRequest req) 306 throws CmsXmlException, CmsLoaderException, CmsException { 307 308 String rootPath = resource.getRootPath(); 309 310 // try to get the requested content from the current request attribute 311 // this is also necessary for historic versions that have been loaded 312 CmsXmlGroupContainer content = (CmsXmlGroupContainer)req.getAttribute(rootPath); 313 314 if (content == null) { 315 // unmarshal XML structure from the file content 316 content = unmarshal(cms, resource); 317 // store the content as request attribute for future read requests 318 req.setAttribute(rootPath, content); 319 } 320 321 // return the result 322 return content; 323 } 324 325 /** 326 * Factory method to unmarshal (generate) a group container instance from a XML document.<p> 327 * 328 * The given encoding is used when marshalling the XML again later.<p> 329 * 330 * <b>Warning:</b><br/> 331 * This method does not support requested historic versions, it always loads the 332 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 333 * for history support.<p> 334 * 335 * @param cms the cms context, if <code>null</code> no link validation is performed 336 * @param document the XML document to generate the group container from 337 * @param encoding the encoding to use when marshalling the group container later 338 * @param resolver the XML entity resolver to use 339 * 340 * @return a group container instance unmarshalled from the String 341 */ 342 public static CmsXmlGroupContainer unmarshal( 343 CmsObject cms, 344 Document document, 345 String encoding, 346 EntityResolver resolver) { 347 348 CmsXmlGroupContainer content = new CmsXmlGroupContainer(cms, document, encoding, resolver); 349 // call prepare for use content handler and return the result 350 return (CmsXmlGroupContainer)content.getHandler().prepareForUse(cms, content); 351 } 352 353 /** 354 * Factory method to unmarshal (generate) a group container instance from a String 355 * that contains XML data.<p> 356 * 357 * The given encoding is used when marshalling the XML again later.<p> 358 * 359 * <b>Warning:</b><br/> 360 * This method does not support requested historic versions, it always loads the 361 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 362 * for history support.<p> 363 * 364 * @param cms the cms context, if <code>null</code> no link validation is performed 365 * @param xmlData the XML data in a String 366 * @param encoding the encoding to use when marshalling the group container later 367 * @param resolver the XML entity resolver to use 368 * 369 * @return a group container instance unmarshalled from the String 370 * 371 * @throws CmsXmlException if something goes wrong 372 */ 373 public static CmsXmlGroupContainer unmarshal( 374 CmsObject cms, 375 String xmlData, 376 String encoding, 377 EntityResolver resolver) 378 throws CmsXmlException { 379 380 // create the XML content object from the provided String 381 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 382 } 383 384 /** 385 * Gets the ADE cache from the ADE manager.<p> 386 * 387 * @return the ADE cache 388 */ 389 private static CmsADECache getCache() { 390 391 return OpenCms.getADEManager().getCache(); 392 } 393 394 /** 395 * Returns the cached group container.<p> 396 * 397 * @param cms the cms context 398 * @param resource the group container resource 399 * @param keepEncoding if to keep the encoding while unmarshalling 400 * 401 * @return the cached group container, or <code>null</code> if not found 402 */ 403 private static CmsXmlGroupContainer getCache(CmsObject cms, CmsResource resource, boolean keepEncoding) { 404 405 if (resource instanceof I_CmsHistoryResource) { 406 return null; 407 } 408 return getCache().getCacheGroupContainer( 409 getCache().getCacheKey(resource.getStructureId(), keepEncoding), 410 cms.getRequestContext().getCurrentProject().isOnlineProject()); 411 } 412 413 /** 414 * Stores the given group container in the cache.<p> 415 * 416 * @param cms the cms context 417 * @param xmlGroupContainer the group container to cache 418 * @param keepEncoding if the encoding was kept while unmarshalling 419 */ 420 private static void setCache(CmsObject cms, CmsXmlGroupContainer xmlGroupContainer, boolean keepEncoding) { 421 422 if (xmlGroupContainer.getFile() instanceof I_CmsHistoryResource) { 423 return; 424 } 425 boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject(); 426 getCache().setCacheGroupContainer( 427 getCache().getCacheKey(xmlGroupContainer.getFile().getStructureId(), keepEncoding), 428 xmlGroupContainer, 429 online); 430 } 431 432}