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 280 // Need to call initDocument again because it needs the file set to fix the nested formatter settings 281 content.initDocument(cms); 282 283 // call prepare for use content handler and return the result 284 CmsXmlContainerPage xmlCntPage = (CmsXmlContainerPage)content.getHandler().prepareForUse(cms, content); 285 286 // set the cache 287 if (!noCache) { 288 setCache(cms, xmlCntPage, keepEncoding); 289 } 290 291 return xmlCntPage; 292 } 293 294 /** 295 * Factory method to unmarshal (read) a container page instance from a OpenCms VFS resource 296 * that contains XML data.<p> 297 * 298 * <b>Warning:</b><br/> 299 * This method does not support requested historic versions, it always loads the 300 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 301 * for history support.<p> 302 * 303 * @param cms the current cms object 304 * @param resource the resource with the XML data to unmarshal 305 * 306 * @return a container page instance unmarshalled from the provided resource 307 * 308 * @throws CmsException if something goes wrong 309 */ 310 public static CmsXmlContainerPage unmarshal(CmsObject cms, CmsResource resource) throws CmsException { 311 312 // check the cache 313 CmsXmlContainerPage content = getCache(cms, resource, true); 314 if (content != null) { 315 return content; 316 } 317 318 content = unmarshal(cms, cms.readFile(resource), true, true); 319 320 // set the cache 321 setCache(cms, content, true); 322 323 return content; 324 } 325 326 /** 327 * Factory method to unmarshal (read) a container page instance from 328 * a resource, using the request attributes as cache.<p> 329 * 330 * @param cms the current OpenCms context object 331 * @param resource the resource to unmarshal 332 * @param req the current request 333 * 334 * @return the unmarshaled xml content, or null if the given resource was not of type {@link org.opencms.file.types.CmsResourceTypeXmlContainerPage} 335 * 336 * @throws CmsException in something goes wrong 337 * @throws CmsLoaderException if no loader for the given <code>resource</code> type ({@link CmsResource#getTypeId()}) is available 338 * @throws CmsXmlException if the given <code>resource</code> is not of type container page 339 */ 340 public static CmsXmlContainerPage unmarshal(CmsObject cms, CmsResource resource, ServletRequest req) 341 throws CmsXmlException, CmsLoaderException, CmsException { 342 343 String rootPath = resource.getRootPath(); 344 345 if (!CmsResourceTypeXmlContainerPage.isContainerPage(resource)) { 346 // sanity check: resource must be of type XML content 347 throw new CmsXmlException( 348 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_TYPE_1, resource.getRootPath())); 349 } 350 351 // try to get the requested content from the current request attribute 352 // this is also necessary for historic versions that have been loaded 353 CmsXmlContainerPage content = (CmsXmlContainerPage)req.getAttribute(rootPath); 354 355 if (content == null) { 356 // unmarshal XML structure from the file content 357 content = unmarshal(cms, resource); 358 // store the content as request attribute for future read requests 359 req.setAttribute(rootPath, content); 360 } 361 362 // return the result 363 return content; 364 } 365 366 /** 367 * Factory method to unmarshal (generate) a container page instance from a XML document.<p> 368 * 369 * The given encoding is used when marshalling the XML again later.<p> 370 * 371 * <b>Warning:</b><br/> 372 * This method does not support requested historic versions, it always loads the 373 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 374 * for history support.<p> 375 * 376 * @param cms the cms context, if <code>null</code> no link validation is performed 377 * @param document the XML document to generate the container page from 378 * @param encoding the encoding to use when marshalling the container page later 379 * @param resolver the XML entity resolver to use 380 * 381 * @return a container page instance unmarshalled from the String 382 */ 383 public static CmsXmlContainerPage unmarshal( 384 CmsObject cms, 385 Document document, 386 String encoding, 387 EntityResolver resolver) { 388 389 CmsXmlContainerPage content = new CmsXmlContainerPage(cms, document, encoding, resolver); 390 // call prepare for use content handler and return the result 391 return (CmsXmlContainerPage)content.getHandler().prepareForUse(cms, content); 392 } 393 394 /** 395 * Factory method to unmarshal (generate) a container page instance from a String 396 * that contains XML data.<p> 397 * 398 * The given encoding is used when marshalling the XML again later.<p> 399 * 400 * <b>Warning:</b><br/> 401 * This method does not support requested historic versions, it always loads the 402 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 403 * for history support.<p> 404 * 405 * @param cms the cms context, if <code>null</code> no link validation is performed 406 * @param xmlData the XML data in a String 407 * @param encoding the encoding to use when marshalling the container page later 408 * @param resolver the XML entity resolver to use 409 * 410 * @return a container page instance unmarshalled from the String 411 * 412 * @throws CmsXmlException if something goes wrong 413 */ 414 public static CmsXmlContainerPage unmarshal(CmsObject cms, String xmlData, String encoding, EntityResolver resolver) 415 throws CmsXmlException { 416 417 // create the XML content object from the provided String 418 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 419 } 420 421 /** 422 * Gets the ADE cache from the ADE manager.<p> 423 * 424 * @return the ADE cache 425 */ 426 private static CmsADECache getCache() { 427 428 return OpenCms.getADEManager().getCache(); 429 } 430 431 /** 432 * Returns the cached container page.<p> 433 * 434 * @param cms the cms context 435 * @param resource the container page resource 436 * @param keepEncoding if to keep the encoding while unmarshalling 437 * 438 * @return the cached container page, or <code>null</code> if not found 439 */ 440 private static CmsXmlContainerPage getCache(CmsObject cms, CmsResource resource, boolean keepEncoding) { 441 442 if (resource instanceof I_CmsHistoryResource) { 443 return null; 444 } 445 return getCache().getCacheContainerPage( 446 getCache().getCacheKey(resource.getStructureId(), keepEncoding), 447 cms.getRequestContext().getCurrentProject().isOnlineProject()); 448 } 449 450 /** 451 * Stores the given container page in the cache.<p> 452 * 453 * @param cms the cms context 454 * @param xmlCntPage the container page to cache 455 * @param keepEncoding if the encoding was kept while unmarshalling 456 */ 457 private static void setCache(CmsObject cms, CmsXmlContainerPage xmlCntPage, boolean keepEncoding) { 458 459 if (xmlCntPage.getFile() instanceof I_CmsHistoryResource) { 460 return; 461 } 462 if (xmlCntPage.hasInvalidatedBrokenLinks()) { 463 // not caching container pages with broken links - they may be 'broken' for permission reasons, 464 // and the cache does not take the current user into account 465 return; 466 } 467 boolean online = cms.getRequestContext().getCurrentProject().isOnlineProject(); 468 getCache().setCacheContainerPage( 469 getCache().getCacheKey(xmlCntPage.getFile().getStructureId(), keepEncoding), 470 xmlCntPage, 471 online); 472 } 473}