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