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.content; 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.types.CmsResourceTypeXmlAdeConfiguration; 035import org.opencms.file.types.CmsResourceTypeXmlContent; 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; 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 XML content object.<p> 056 * 057 * @since 6.0.0 058 */ 059public final class CmsXmlContentFactory { 060 061 /** 062 * No instances of this class should be created.<p> 063 */ 064 private CmsXmlContentFactory() { 065 066 // noop 067 } 068 069 /** 070 * Creates a new XML content based on a resource type.<p> 071 * 072 * @param cms the current OpenCms context 073 * @param locale the locale to generate the default content for 074 * @param resourceType the resource type for which the document should be created 075 * 076 * @return the created XML content 077 * 078 * @throws CmsXmlException if something goes wrong 079 */ 080 public static CmsXmlContent createDocument(CmsObject cms, Locale locale, CmsResourceTypeXmlContent resourceType) 081 throws CmsXmlException { 082 083 String schema = resourceType.getSchema(); 084 CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema); 085 CmsXmlContent xmlContent = CmsXmlContentFactory.createDocument( 086 cms, 087 locale, 088 OpenCms.getSystemInfo().getDefaultEncoding(), 089 contentDefinition); 090 return xmlContent; 091 } 092 093 /** 094 * Create a new instance of an XML content based on the given default content, 095 * hat will have all language nodes of the default content and ensures the presence of the given locale.<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 modelUri the absolute path to the XML content file acting as model 102 * 103 * @throws CmsException in case the model file is not found or not valid 104 * 105 * @return the created XML content 106 */ 107 public static CmsXmlContent createDocument(CmsObject cms, Locale locale, String modelUri) throws CmsException { 108 109 // create the XML content 110 CmsXmlContent content = new CmsXmlContent(cms, locale, modelUri); 111 // call prepare for use content handler and return the result 112 return content.getContentDefinition().getContentHandler().prepareForUse(cms, content); 113 } 114 115 /** 116 * Create a new instance of an XML content based on the given content definiton, 117 * that will have one language node for the given locale all initialized with default values.<p> 118 * 119 * The given encoding is used when marshalling the XML again later.<p> 120 * 121 * @param cms the current users OpenCms content 122 * @param locale the locale to generate the default content for 123 * @param encoding the encoding to use when marshalling the XML content later 124 * @param contentDefinition the content definiton to create the content for 125 * 126 * @return the created XML content 127 */ 128 public static CmsXmlContent createDocument( 129 CmsObject cms, 130 Locale locale, 131 String encoding, 132 CmsXmlContentDefinition contentDefinition) { 133 134 // create the XML content 135 CmsXmlContent content = new CmsXmlContent(cms, locale, encoding, contentDefinition); 136 // call prepare for use content handler and return the result 137 return content.getHandler().prepareForUse(cms, content); 138 } 139 140 /** 141 * Factory method to unmarshal (generate) a XML content instance from a byte array 142 * that contains XML data.<p> 143 * 144 * When unmarshalling, the encoding is read directly from the XML header of the byte array. 145 * The given encoding is used only when marshalling the XML again later.<p> 146 * 147 * <b>Warning:</b><br/> 148 * This method does not support requested historic versions, it always loads the 149 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 150 * for history support.<p> 151 * 152 * @param cms the cms context 153 * @param xmlData the XML data in a byte array 154 * @param encoding the encoding to use when marshalling the XML content later 155 * @param resolver the XML entitiy resolver to use 156 * 157 * @return a XML content instance unmarshalled from the byte array 158 * 159 * @throws CmsXmlException if something goes wrong 160 */ 161 public static CmsXmlContent unmarshal(CmsObject cms, byte[] xmlData, String encoding, EntityResolver resolver) 162 throws CmsXmlException { 163 164 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 165 } 166 167 /** 168 * Factory method to unmarshal (read) a XML content instance from a OpenCms VFS file 169 * that contains XML data.<p> 170 * 171 * <b>Warning:</b><br/> 172 * This method does not support requested historic versions, it always loads the 173 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 174 * for history support.<p> 175 * 176 * @param cms the current cms object 177 * @param file the file with the XML data to unmarshal 178 * 179 * @return a XML page instance unmarshalled from the provided file 180 * 181 * @throws CmsXmlException if something goes wrong 182 */ 183 public static CmsXmlContent unmarshal(CmsObject cms, CmsFile file) throws CmsXmlException { 184 185 return unmarshal(cms, file, true); 186 } 187 188 /** 189 * Factory method to unmarshal (read) a XML content instance from a OpenCms VFS file 190 * that contains XML data, using wither the encoding set 191 * in the XML file header, or the encoding set in the VFS file property.<p> 192 * 193 * If you are not sure about the implications of the encoding issues, 194 * use {@link #unmarshal(CmsObject, CmsFile)} instead.<p> 195 * 196 * <b>Warning:</b><br/> 197 * This method does not support requested historic versions, it always loads the 198 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 199 * for history support.<p> 200 * 201 * @param cms the current cms object 202 * @param file the file with the XML data to unmarshal 203 * @param keepEncoding if true, the encoding spefified in the XML header is used, 204 * otherwise the encoding from the VFS file property is used 205 * 206 * @return a XML content instance unmarshalled from the provided file 207 * 208 * @throws CmsXmlException if something goes wrong 209 */ 210 public static CmsXmlContent unmarshal(CmsObject cms, CmsFile file, boolean keepEncoding) throws CmsXmlException { 211 212 byte[] contentBytes = file.getContents(); 213 String filename = cms.getSitePath(file); 214 215 String encoding = null; 216 if (OpenCms.getResourceManager().hasResourceType(file.getTypeId())) { 217 if (OpenCms.getResourceManager().getResourceType(file) instanceof CmsResourceTypeXmlAdeConfiguration) { 218 encoding = "UTF-8"; 219 } 220 } 221 if (encoding == null) { 222 try { 223 encoding = cms.readPropertyObject( 224 file, 225 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 226 true).getValue(); 227 } catch (@SuppressWarnings("unused") CmsException e) { 228 // encoding will be null 229 } 230 } 231 if (encoding == null) { 232 encoding = OpenCms.getSystemInfo().getDefaultEncoding(); 233 } else { 234 encoding = CmsEncoder.lookupEncoding(encoding, null); 235 if (encoding == null) { 236 throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, filename)); 237 } 238 } 239 240 CmsXmlContent content; 241 if (contentBytes.length > 0) { 242 // content is initialized 243 if (keepEncoding) { 244 // use the encoding from the content 245 content = unmarshal(cms, contentBytes, encoding, new CmsXmlEntityResolver(cms)); 246 } else { 247 // use the encoding from the file property 248 // this usually only triggered by a save operation 249 try { 250 String contentStr = new String(contentBytes, encoding); 251 content = unmarshal(cms, contentStr, encoding, new CmsXmlEntityResolver(cms)); 252 } catch (UnsupportedEncodingException e) { 253 // this will not happen since the encodig has already been validated 254 throw new CmsXmlException( 255 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, filename), 256 e); 257 } 258 } 259 } else { 260 // content is empty 261 content = new CmsXmlContent(cms, DocumentHelper.createDocument(), encoding, new CmsXmlEntityResolver(cms)); 262 } 263 264 // set the file 265 content.setFile(file); 266 // call prepare for use content handler and return the result 267 return content.getHandler().prepareForUse(cms, content); 268 } 269 270 /** 271 * Factory method to unmarshal (read) a XML content instance from 272 * a resource, using the request attributes as cache.<p> 273 * 274 * @param cms the current OpenCms context object 275 * @param resource the resource to unmarshal 276 * @param req the current request 277 * 278 * @return the unmarshaled xml content, or null if the given resource was not of type {@link org.opencms.file.types.CmsResourceTypeXmlContent} 279 * 280 * @throws CmsException in something goes wrong 281 * @throws CmsLoaderException if no loader for the given <code>resource</code> type ({@link CmsResource#getTypeId()}) is available 282 * @throws CmsXmlException if the given <code>resource</code> is not of type xml content 283 */ 284 public static CmsXmlContent unmarshal(CmsObject cms, CmsResource resource, ServletRequest req) 285 throws CmsXmlException, CmsLoaderException, CmsException { 286 287 String rootPath = resource.getRootPath(); 288 289 if (!CmsResourceTypeXmlContent.isXmlContent(resource)) { 290 // sanity check: resource must be of type XML content 291 throw new CmsXmlException( 292 Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_TYPE_1, cms.getSitePath(resource))); 293 } 294 295 // try to get the requested content from the current request attribute 296 // this is also necessary for historic versions that have been loaded 297 CmsXmlContent content = (CmsXmlContent)req.getAttribute(rootPath); 298 299 if (content == null) { 300 // unmarshal XML structure from the file content 301 CmsFile file = resource instanceof CmsFile ? (CmsFile)resource : cms.readFile(resource); 302 content = unmarshal(cms, file); 303 // store the content as request attribute for future read requests 304 req.setAttribute(rootPath, content); 305 } 306 307 // return the result 308 return content; 309 } 310 311 /** 312 * Factory method to unmarshal (generate) a XML content instance from a XML document.<p> 313 * 314 * The given encoding is used when marshalling the XML again later.<p> 315 * 316 * <b>Warning:</b><br/> 317 * This method does not support requested historic versions, it always loads the 318 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 319 * for history support.<p> 320 * 321 * @param cms the cms context, if <code>null</code> no link validation is performed 322 * @param document the XML document to generate the XML content from 323 * @param encoding the encoding to use when marshalling the XML content later 324 * @param resolver the XML entitiy resolver to use 325 * 326 * @return a XML content instance unmarshalled from the String 327 */ 328 public static CmsXmlContent unmarshal(CmsObject cms, Document document, String encoding, EntityResolver resolver) { 329 330 CmsXmlContent content = new CmsXmlContent(cms, document, encoding, resolver); 331 // call prepare for use content handler and return the result 332 return content.getHandler().prepareForUse(cms, content); 333 } 334 335 /** 336 * Factory method to unmarshal (generate) a XML content instance from a String 337 * that contains XML data.<p> 338 * 339 * The given encoding is used when marshalling the XML again later.<p> 340 * 341 * <b>Warning:</b><br/> 342 * This method does not support requested historic versions, it always loads the 343 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 344 * for history support.<p> 345 * 346 * @param cms the cms context, if <code>null</code> no link validation is performed 347 * @param xmlData the XML data in a String 348 * @param encoding the encoding to use when marshalling the XML content later 349 * @param resolver the XML entitiy resolver to use 350 * 351 * @return a XML content instance unmarshalled from the String 352 * 353 * @throws CmsXmlException if something goes wrong 354 */ 355 public static CmsXmlContent unmarshal(CmsObject cms, String xmlData, String encoding, EntityResolver resolver) 356 throws CmsXmlException { 357 358 // create the XML content object from the provided String 359 return unmarshal(cms, CmsXmlUtils.unmarshalHelper(xmlData, resolver), encoding, resolver); 360 } 361 362 /** 363 * Factory method to unmarshal (generate) a XML content instance from a String 364 * that contains XML data.<p> 365 * 366 * The given encoding is used when marshalling the XML again later.<p> 367 * 368 * Since no {@link CmsObject} is available, no link validation is performed!<p> 369 * 370 * <b>Warning:</b><br/> 371 * This method does not support requested historic versions, it always loads the 372 * most recent version. Use <code>{@link #unmarshal(CmsObject, CmsResource, ServletRequest)}</code> 373 * for history support.<p> 374 * 375 * @param xmlData the XML data in a String 376 * @param encoding the encoding to use when marshalling the XML content later 377 * @param resolver the XML entity resolver to use 378 * 379 * @return a XML content instance unmarshalled from the String 380 * 381 * @throws CmsXmlException if something goes wrong 382 */ 383 public static CmsXmlContent unmarshal(String xmlData, String encoding, EntityResolver resolver) 384 throws CmsXmlException { 385 386 return unmarshal(null, xmlData, encoding, resolver); 387 } 388}