001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://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.ade.containerpage.shared.CmsContainer; 031import org.opencms.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 036import org.opencms.i18n.CmsEncoder; 037import org.opencms.i18n.CmsLocaleManager; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.main.OpenCms; 041import org.opencms.relations.CmsLink; 042import org.opencms.relations.CmsRelationType; 043import org.opencms.util.CmsMacroResolver; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.util.CmsUUID; 046import org.opencms.xml.CmsXmlContentDefinition; 047import org.opencms.xml.CmsXmlException; 048import org.opencms.xml.CmsXmlGenericWrapper; 049import org.opencms.xml.CmsXmlUtils; 050import org.opencms.xml.content.CmsXmlContent; 051import org.opencms.xml.content.CmsXmlContentMacroVisitor; 052import org.opencms.xml.content.CmsXmlContentProperty; 053import org.opencms.xml.content.CmsXmlContentPropertyHelper; 054import org.opencms.xml.page.CmsXmlPage; 055import org.opencms.xml.types.CmsXmlNestedContentDefinition; 056import org.opencms.xml.types.CmsXmlVfsFileValue; 057import org.opencms.xml.types.I_CmsXmlContentValue; 058import org.opencms.xml.types.I_CmsXmlSchemaType; 059 060import java.util.ArrayList; 061import java.util.HashMap; 062import java.util.HashSet; 063import java.util.Iterator; 064import java.util.LinkedHashSet; 065import java.util.List; 066import java.util.Locale; 067import java.util.Map; 068import java.util.Set; 069 070import org.apache.commons.logging.Log; 071 072import org.dom4j.Document; 073import org.dom4j.Element; 074import org.xml.sax.EntityResolver; 075 076/** 077 * Implementation of a object used to access and manage the xml data of a group container.<p> 078 * 079 * In addition to the XML content interface. It also provides access to more comfortable beans.<p> 080 * 081 * @since 8.0.0 082 */ 083public class CmsXmlGroupContainer extends CmsXmlContent { 084 085 /** XML node name constants. */ 086 public enum XmlNode { 087 088 /** Container description node name. */ 089 Description, 090 /** Container elements node name. */ 091 Element, 092 /** Main node name. */ 093 GroupContainers, 094 /** Container title node name. */ 095 Title, 096 /** Container type node name. */ 097 Type, 098 /** File list URI node name. */ 099 Uri; 100 } 101 102 /** The log object for this class. */ 103 private static final Log LOG = CmsLog.getLog(CmsXmlGroupContainer.class); 104 105 /** The group container objects. */ 106 private Map<Locale, CmsGroupContainerBean> m_groupContainers; 107 108 /** 109 * Hides the public constructor.<p> 110 */ 111 protected CmsXmlGroupContainer() { 112 113 // do nothing 114 } 115 116 /** 117 * Creates a new group container based on the provided XML document.<p> 118 * 119 * The given encoding is used when marshalling the XML again later.<p> 120 * 121 * @param cms the cms context, if <code>null</code> no link validation is performed 122 * @param document the document to create the container page from 123 * @param encoding the encoding of the container page 124 * @param resolver the XML entity resolver to use 125 */ 126 protected CmsXmlGroupContainer(CmsObject cms, Document document, String encoding, EntityResolver resolver) { 127 128 // must set document first to be able to get the content definition 129 m_document = document; 130 // for the next line to work the document must already be available 131 m_contentDefinition = getContentDefinition(resolver); 132 // initialize the XML content structure 133 initDocument(cms, m_document, encoding, m_contentDefinition); 134 } 135 136 /** 137 * Create a new group container based on the given default content, 138 * that will have all language nodes of the default content and ensures the presence of the given locale.<p> 139 * 140 * The given encoding is used when marshalling the XML again later.<p> 141 * 142 * @param cms the current users OpenCms content 143 * @param locale the locale to generate the default content for 144 * @param modelUri the absolute path to the container page file acting as model 145 * 146 * @throws CmsException in case the model file is not found or not valid 147 */ 148 protected CmsXmlGroupContainer(CmsObject cms, Locale locale, String modelUri) 149 throws CmsException { 150 151 // init model from given modelUri 152 CmsFile modelFile = cms.readFile(modelUri, CmsResourceFilter.ONLY_VISIBLE_NO_DELETED); 153 CmsXmlGroupContainer model = CmsXmlGroupContainerFactory.unmarshal(cms, modelFile); 154 155 // initialize macro resolver to use on model file values 156 CmsMacroResolver macroResolver = CmsMacroResolver.newInstance().setCmsObject(cms); 157 158 // content definition must be set here since it's used during document creation 159 m_contentDefinition = model.getContentDefinition(); 160 // get the document from the default content 161 Document document = (Document)model.m_document.clone(); 162 // initialize the XML content structure 163 initDocument(cms, document, model.getEncoding(), m_contentDefinition); 164 // resolve eventual macros in the nodes 165 visitAllValuesWith(new CmsXmlContentMacroVisitor(cms, macroResolver)); 166 if (!hasLocale(locale)) { 167 // required locale not present, add it 168 try { 169 addLocale(cms, locale); 170 } catch (CmsXmlException e) { 171 // this can not happen since the locale does not exist 172 } 173 } 174 } 175 176 /** 177 * Create a new container page based on the given content definition, 178 * that will have one language node for the given locale all initialized with default values.<p> 179 * 180 * The given encoding is used when marshalling the XML again later.<p> 181 * 182 * @param cms the current users OpenCms content 183 * @param locale the locale to generate the default content for 184 * @param encoding the encoding to use when marshalling the container page later 185 * @param contentDefinition the content definition to create the content for 186 */ 187 protected CmsXmlGroupContainer( 188 CmsObject cms, 189 Locale locale, 190 String encoding, 191 CmsXmlContentDefinition contentDefinition) { 192 193 // content definition must be set here since it's used during document creation 194 m_contentDefinition = contentDefinition; 195 // create the XML document according to the content definition 196 Document document = m_contentDefinition.createDocument(cms, this, locale); 197 // initialize the XML content structure 198 initDocument(cms, document, encoding, m_contentDefinition); 199 } 200 201 /** 202 * Removes all locales from the element group XML.<p> 203 * 204 * @throws CmsXmlException if something goes wrong 205 */ 206 public void clearLocales() throws CmsXmlException { 207 208 for (Locale locale : getLocales()) { 209 removeLocale(locale); 210 } 211 } 212 213 /** 214 * Returns the group container bean for the given locale.<p> 215 * 216 * @param cms the cms context 217 * 218 * @return the group container bean 219 */ 220 public CmsGroupContainerBean getGroupContainer(CmsObject cms) { 221 222 if (m_groupContainers.containsKey(CmsLocaleManager.MASTER_LOCALE)) { 223 return m_groupContainers.get(CmsLocaleManager.MASTER_LOCALE); 224 } else if (!m_groupContainers.isEmpty()) { 225 return m_groupContainers.get(m_groupContainers.keySet().iterator().next()); 226 } else { 227 return null; 228 } 229 } 230 231 /** 232 * @see org.opencms.xml.content.CmsXmlContent#isAutoCorrectionEnabled() 233 */ 234 @Override 235 public boolean isAutoCorrectionEnabled() { 236 237 return true; 238 } 239 240 /** 241 * Saves given container page in the current locale, and not only in memory but also to VFS.<p> 242 * 243 * @param cms the current cms context 244 * @param groupContainer the group-container page to save 245 * @param locale the locale to save 246 * 247 * @throws CmsException if something goes wrong 248 */ 249 public void save(CmsObject cms, CmsGroupContainerBean groupContainer, Locale locale) throws CmsException { 250 251 CmsFile file = getFile(); 252 253 // lock the file 254 cms.lockResourceTemporary(cms.getSitePath(file)); 255 256 // wipe the locale 257 if (hasLocale(locale)) { 258 removeLocale(locale); 259 } 260 261 addLocale(cms, locale); 262 263 // add the nodes to the raw XML structure 264 Element parent = getLocaleNode(locale); 265 saveGroupContainer(cms, parent, groupContainer); 266 267 // generate bookmarks 268 initDocument(m_document, m_encoding, m_contentDefinition); 269 270 // write to VFS 271 file.setContents(marshal()); 272 cms.writeFile(file); 273 } 274 275 /** 276 * Fills a {@link CmsXmlVfsFileValue} with the resource identified by the given id.<p> 277 * 278 * @param cms the current CMS context 279 * @param element the XML element to fill 280 * @param res the resource to use 281 */ 282 protected void fillResource(CmsObject cms, Element element, CmsResource res) { 283 284 String xpath = element.getPath(); 285 int pos = xpath.lastIndexOf("/" + XmlNode.GroupContainers.name() + "/"); 286 if (pos > 0) { 287 xpath = xpath.substring(pos + 1); 288 } 289 CmsRelationType type = getHandler().getRelationType(xpath); 290 CmsXmlVfsFileValue.fillEntry(element, res.getStructureId(), res.getRootPath(), type); 291 } 292 293 /** 294 * @see org.opencms.xml.A_CmsXmlDocument#initDocument(org.dom4j.Document, java.lang.String, org.opencms.xml.CmsXmlContentDefinition) 295 */ 296 @Override 297 protected void initDocument(Document document, String encoding, CmsXmlContentDefinition definition) { 298 299 m_document = document; 300 m_contentDefinition = definition; 301 m_encoding = CmsEncoder.lookupEncoding(encoding, encoding); 302 m_elementLocales = new HashMap<String, Set<Locale>>(); 303 m_elementNames = new HashMap<Locale, LinkedHashSet<String>>(); 304 m_locales = new HashSet<Locale>(); 305 m_groupContainers = new HashMap<Locale, CmsGroupContainerBean>(); 306 clearBookmarks(); 307 308 // initialize the bookmarks 309 for (Iterator<Element> itGroupContainers = CmsXmlGenericWrapper.elementIterator( 310 m_document.getRootElement()); itGroupContainers.hasNext();) { 311 Element cntPage = itGroupContainers.next(); 312 313 try { 314 Locale locale = CmsLocaleManager.getLocale( 315 cntPage.attribute(CmsXmlContentDefinition.XSD_ATTRIBUTE_VALUE_LANGUAGE).getValue()); 316 317 addLocale(locale); 318 Element groupContainer = cntPage.element(XmlNode.GroupContainers.name()); 319 320 // container itself 321 int cntIndex = CmsXmlUtils.getXpathIndexInt(groupContainer.getUniquePath(cntPage)); 322 String cntPath = CmsXmlUtils.createXpathElement(groupContainer.getName(), cntIndex); 323 I_CmsXmlSchemaType cntSchemaType = definition.getSchemaType(groupContainer.getName()); 324 I_CmsXmlContentValue cntValue = cntSchemaType.createValue(this, groupContainer, locale); 325 addBookmark(cntPath, locale, true, cntValue); 326 CmsXmlContentDefinition cntDef = ((CmsXmlNestedContentDefinition)cntSchemaType).getNestedContentDefinition(); 327 328 //title 329 Element title = groupContainer.element(XmlNode.Title.name()); 330 addBookmarkForElement(title, locale, groupContainer, cntPath, cntDef); 331 332 //description 333 Element description = groupContainer.element(XmlNode.Description.name()); 334 addBookmarkForElement(description, locale, groupContainer, cntPath, cntDef); 335 336 // types 337 Set<String> types = new HashSet<String>(); 338 for (Iterator<Element> itTypes = CmsXmlGenericWrapper.elementIterator( 339 groupContainer, 340 XmlNode.Type.name()); itTypes.hasNext();) { 341 Element type = itTypes.next(); 342 addBookmarkForElement(type, locale, groupContainer, cntPath, cntDef); 343 String typeName = type.getTextTrim(); 344 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(typeName)) { 345 types.addAll(CmsContainer.splitType(typeName)); 346 } 347 } 348 349 List<CmsContainerElementBean> elements = new ArrayList<CmsContainerElementBean>(); 350 // Elements 351 for (Element element : CmsXmlGenericWrapper.elementIterable(groupContainer, XmlNode.Element.name())) { 352 // element itself 353 int elemIndex = CmsXmlUtils.getXpathIndexInt(element.getUniquePath(groupContainer)); 354 String elemPath = CmsXmlUtils.concatXpath( 355 cntPath, 356 CmsXmlUtils.createXpathElement(element.getName(), elemIndex)); 357 I_CmsXmlSchemaType elemSchemaType = cntDef.getSchemaType(element.getName()); 358 I_CmsXmlContentValue elemValue = elemSchemaType.createValue(this, element, locale); 359 addBookmark(elemPath, locale, true, elemValue); 360 CmsXmlContentDefinition elemDef = ((CmsXmlNestedContentDefinition)elemSchemaType).getNestedContentDefinition(); 361 362 // uri 363 Element uri = element.element(XmlNode.Uri.name()); 364 addBookmarkForElement(uri, locale, element, elemPath, elemDef); 365 Element uriLink = uri.element(CmsXmlPage.NODE_LINK); 366 CmsUUID elementId = null; 367 if (uriLink == null) { 368 // this can happen when adding the elements node to the xml content 369 // it is not dangerous since the link has to be set before saving 370 } else { 371 elementId = new CmsLink(uriLink).getStructureId(); 372 } 373 374 // Element createNewElement = element.element(CmsXmlContainerPage.XmlNode.CreateNew.name()); 375 // boolean createNew = (createNewElement != null) 376 // && Boolean.parseBoolean(createNewElement.getStringValue()); 377 378 // propeties 379 Map<String, String> propertiesMap = CmsXmlContentPropertyHelper.readProperties( 380 this, 381 locale, 382 element, 383 elemPath, 384 elemDef); 385 386 if (elementId != null) { 387 elements.add(new CmsContainerElementBean(elementId, null, propertiesMap, false)); 388 } 389 } 390 m_groupContainers.put( 391 locale, 392 new CmsGroupContainerBean(title.getText(), description.getText(), elements, types)); 393 } catch (NullPointerException e) { 394 LOG.error( 395 org.opencms.xml.content.Messages.get().getBundle().key( 396 org.opencms.xml.content.Messages.LOG_XMLCONTENT_INIT_BOOKMARKS_0), 397 e); 398 } 399 } 400 } 401 402 /** 403 * Adds the given container page to the given element.<p> 404 * 405 * @param cms the current CMS object 406 * @param parent the element to add it 407 * @param groupContainer the container page to add 408 * 409 * @throws CmsException if something goes wrong 410 */ 411 protected void saveGroupContainer(CmsObject cms, Element parent, CmsGroupContainerBean groupContainer) 412 throws CmsException { 413 414 parent.clearContent(); 415 Element groupContainerElem = parent.addElement(XmlNode.GroupContainers.name()); 416 417 groupContainerElem.addElement(XmlNode.Title.name()).addCDATA(groupContainer.getTitle()); 418 groupContainerElem.addElement(XmlNode.Description.name()).addCDATA(groupContainer.getDescription()); 419 420 for (String type : groupContainer.getTypes()) { 421 groupContainerElem.addElement(XmlNode.Type.name()).addCDATA(type); 422 } 423 424 // the elements 425 for (CmsContainerElementBean element : groupContainer.getElements()) { 426 CmsResource res = cms.readResource(element.getId(), CmsResourceFilter.IGNORE_EXPIRATION); 427 if (OpenCms.getResourceManager().getResourceType(res.getTypeId()).getTypeName().equals( 428 CmsResourceTypeXmlContainerPage.GROUP_CONTAINER_TYPE_NAME)) { 429 LOG.warn(Messages.get().container(Messages.LOG_WARN_ELEMENT_GROUP_INSIDE_ELEMENT_GROUP_0)); 430 continue; 431 } 432 Element elemElement = groupContainerElem.addElement(XmlNode.Element.name()); 433 434 // the element 435 Element uriElem = elemElement.addElement(XmlNode.Uri.name()); 436 fillResource(cms, uriElem, res); 437 438 // the properties 439 Map<String, String> properties = element.getIndividualSettings(); 440 Map<String, CmsXmlContentProperty> propertiesConf = OpenCms.getADEManager().getElementSettings(cms, res); 441 442 CmsXmlContentPropertyHelper.saveProperties(cms, elemElement, properties, propertiesConf, true); 443 } 444 } 445 446 /** 447 * @see org.opencms.xml.content.CmsXmlContent#setFile(org.opencms.file.CmsFile) 448 */ 449 @Override 450 protected void setFile(CmsFile file) { 451 452 // just for visibility from the factory 453 super.setFile(file); 454 } 455}