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