001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://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: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://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 javax.servlet.ServletRequest; 049 050import org.dom4j.Document; 051import org.dom4j.DocumentHelper; 052import org.xml.sax.EntityResolver; 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) throws CmsXmlException { 143 144 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 145 } 146 147 /** 148 * Factory method to unmarshal (read) a group container instance from a OpenCms VFS file 149 * that contains XML data.<p> 150 * 151 * <b>Warning:</b><br/> 152 * This method does not support requested historic versions, it always loads the 153 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 154 * for history support.<p> 155 * 156 * @param cms the current cms object 157 * @param file the file with the XML data to unmarshal 158 * 159 * @return a group container instance unmarshalled from the provided file 160 * 161 * @throws CmsXmlException if something goes wrong 162 */ 163 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsFile file) throws CmsXmlException { 164 165 return unmarshal(cms, file, true); 166 } 167 168 /** 169 * Factory method to unmarshal (read) a group container instance from a OpenCms VFS file 170 * that contains XML data, using wither the encoding set 171 * in the XML file header, or the encoding set in the VFS file property.<p> 172 * 173 * If you are not sure about the implications of the encoding issues, 174 * use {@link #unmarshal(CmsObject, CmsFile)} instead.<p> 175 * 176 * <b>Warning:</b><br/> 177 * This method does not support requested historic versions, it always loads the 178 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 179 * for history support.<p> 180 * 181 * @param cms the current cms object 182 * @param file the file with the XML data to unmarshal 183 * @param keepEncoding if <code>true</code>, the encoding specified in the XML header is used, 184 * otherwise the encoding from the VFS file property is used 185 * 186 * @return a group container instance unmarshalled from the provided file 187 * 188 * @throws CmsXmlException if something goes wrong 189 */ 190 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsFile file, boolean keepEncoding) 191 throws CmsXmlException { 192 193 // check the cache 194 CmsXmlGroupContainer content = getCache(cms, file, keepEncoding); 195 if (content != null) { 196 return content; 197 } 198 199 // not found in cache, read as normally 200 byte[] contentBytes = file.getContents(); 201 String filename = cms.getSitePath(file); 202 203 String encoding = null; 204 try { 205 encoding = cms.readPropertyObject( 206 filename, 207 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 208 true).getValue(); 209 } catch (CmsException e) { 210 // encoding will be null 211 } 212 if (encoding == null) { 213 encoding = OpenCms.getSystemInfo().getDefaultEncoding(); 214 } else { 215 encoding = CmsEncoder.lookupEncoding(encoding, null); 216 if (encoding == null) { 217 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, filename)); 218 } 219 } 220 221 if (contentBytes.length > 0) { 222 // content is initialized 223 if (keepEncoding) { 224 // use the encoding from the content 225 content = unmarshal(cms, contentBytes, encoding, new CmsXmlEntityResolver(cms)); 226 } else { 227 // use the encoding from the file property 228 // this usually only triggered by a save operation 229 try { 230 String contentStr = new String(contentBytes, encoding); 231 content = unmarshal(cms, contentStr, encoding, new CmsXmlEntityResolver(cms)); 232 } catch (UnsupportedEncodingException e) { 233 // this will not happen since the encoding has already been validated 234 throw new CmsXmlException( 235 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, filename)); 236 } 237 } 238 } else { 239 // content is empty 240 content = new CmsXmlGroupContainer( 241 cms, 242 DocumentHelper.createDocument(), 243 encoding, 244 new CmsXmlEntityResolver(cms)); 245 } 246 247 // set the file 248 content.setFile(file); 249 // call prepare for use content handler and return the result 250 CmsXmlGroupContainer xmlGroupContainer = (CmsXmlGroupContainer)content.getHandler().prepareForUse(cms, content); 251 252 // set the cache 253 setCache(cms, xmlGroupContainer, keepEncoding); 254 255 return xmlGroupContainer; 256 } 257 258 /** 259 * Factory method to unmarshal (read) a group container instance from a OpenCms VFS resource 260 * that contains XML data.<p> 261 * 262 * <b>Warning:</b><br/> 263 * This method does not support requested historic versions, it always loads the 264 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 265 * for history support.<p> 266 * 267 * @param cms the current cms object 268 * @param resource the resource with the XML data to unmarshal 269 * 270 * @return a group container instance unmarshalled from the provided resource 271 * 272 * @throws CmsException if something goes wrong 273 */ 274 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsResource resource) throws CmsException { 275 276 // check the cache 277 CmsXmlGroupContainer content = getCache(cms, resource, true); 278 if (content != null) { 279 return content; 280 } 281 282 content = unmarshal(cms, cms.readFile(resource), true); 283 284 // set the cache 285 setCache(cms, content, true); 286 287 return content; 288 } 289 290 /** 291 * Factory method to unmarshal (read) a group container instance from 292 * a resource, using the request attributes as cache.<p> 293 * 294 * @param cms the current OpenCms context object 295 * @param resource the resource to unmarshal 296 * @param req the current request 297 * 298 * @return the unmarshaled xml content 299 * 300 * @throws CmsException in something goes wrong 301 * @throws CmsLoaderException if no loader for the given <code>resource</code> type ({@link CmsResource#getTypeId()}) is available 302 * @throws CmsXmlException if the given <code>resource</code> is not of type group container 303 */ 304 public static CmsXmlGroupContainer unmarshal(CmsObject cms, CmsResource resource, ServletRequest req) 305 throws CmsXmlException, CmsLoaderException, CmsException { 306 307 String rootPath = resource.getRootPath(); 308 309 // try to get the requested content from the current request attribute 310 // this is also necessary for historic versions that have been loaded 311 CmsXmlGroupContainer content = (CmsXmlGroupContainer)req.getAttribute(rootPath); 312 313 if (content == null) { 314 // unmarshal XML structure from the file content 315 content = unmarshal(cms, resource); 316 // store the content as request attribute for future read requests 317 req.setAttribute(rootPath, content); 318 } 319 320 // return the result 321 return content; 322 } 323 324 /** 325 * Factory method to unmarshal (generate) a group container instance from a XML document.<p> 326 * 327 * The given encoding is used when marshalling the XML again later.<p> 328 * 329 * <b>Warning:</b><br/> 330 * This method does not support requested historic versions, it always loads the 331 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 332 * for history support.<p> 333 * 334 * @param cms the cms context, if <code>null</code> no link validation is performed 335 * @param document the XML document to generate the group container from 336 * @param encoding the encoding to use when marshalling the group container later 337 * @param resolver the XML entity resolver to use 338 * 339 * @return a group container instance unmarshalled from the String 340 */ 341 public static CmsXmlGroupContainer unmarshal( 342 CmsObject cms, 343 Document document, 344 String encoding, 345 EntityResolver resolver) { 346 347 CmsXmlGroupContainer content = new CmsXmlGroupContainer(cms, document, encoding, resolver); 348 // call prepare for use content handler and return the result 349 return (CmsXmlGroupContainer)content.getHandler().prepareForUse(cms, content); 350 } 351 352 /** 353 * Factory method to unmarshal (generate) a group container instance from a String 354 * that contains XML data.<p> 355 * 356 * The given encoding is used when marshalling the XML again later.<p> 357 * 358 * <b>Warning:</b><br/> 359 * This method does not support requested historic versions, it always loads the 360 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 361 * for history support.<p> 362 * 363 * @param cms the cms context, if <code>null</code> no link validation is performed 364 * @param xmlData the XML data in a String 365 * @param encoding the encoding to use when marshalling the group container later 366 * @param resolver the XML entity resolver to use 367 * 368 * @return a group container instance unmarshalled from the String 369 * 370 * @throws CmsXmlException if something goes wrong 371 */ 372 public static CmsXmlGroupContainer unmarshal( 373 CmsObject cms, 374 String xmlData, 375 String encoding, 376 EntityResolver resolver) throws CmsXmlException { 377 378 // create the XML content object from the provided String 379 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 380 } 381 382 /** 383 * Gets the ADE cache from the ADE manager.<p> 384 * 385 * @return the ADE cache 386 */ 387 private static CmsADECache getCache() { 388 389 return OpenCms.getADEManager().getCache(); 390 } 391 392 /** 393 * Returns the cached group container.<p> 394 * 395 * @param cms the cms context 396 * @param resource the group container resource 397 * @param keepEncoding if to keep the encoding while unmarshalling 398 * 399 * @return the cached group container, or <code>null</code> if not found 400 */ 401 private static CmsXmlGroupContainer getCache(CmsObject cms, CmsResource resource, boolean keepEncoding) { 402 403 if (resource instanceof I_CmsHistoryResource) { 404 return null; 405 } 406 return getCache().getCacheGroupContainer( 407 getCache().getCacheKey(resource.getStructureId(), keepEncoding), 408 cms.getRequestContext().getCurrentProject().isOnlineProject()); 409 } 410 411 /** 412 * Stores the given group container in the cache.<p> 413 * 414 * @param cms the cms context 415 * @param xmlGroupContainer the group container to cache 416 * @param keepEncoding if the encoding was kept while unmarshalling 417 */ 418 private static void setCache(CmsObject cms, CmsXmlGroupContainer xmlGroupContainer, boolean keepEncoding) { 419 420 if (xmlGroupContainer.getFile() instanceof I_CmsHistoryResource) { 421 return; 422 } 423 boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject(); 424 getCache().setCacheGroupContainer( 425 getCache().getCacheKey(xmlGroupContainer.getFile().getStructureId(), keepEncoding), 426 xmlGroupContainer, 427 online); 428 } 429 430}