001/* 002 * File : $Source$ 003 * Date : $Date$ 004 * Version: $Revision$ 005 * 006 * This library is part of OpenCms - 007 * the Open Source Content Management System 008 * 009 * Copyright (C) 2002 - 2011 Alkacon Software (http://www.alkacon.com) 010 * 011 * This library is free software; you can redistribute it and/or 012 * modify it under the terms of the GNU Lesser General Public 013 * License as published by the Free Software Foundation; either 014 * version 2.1 of the License, or (at your option) any later version. 015 * 016 * This library is distributed in the hope that it will be useful, 017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 * Lesser General Public License for more details. 020 * 021 * For further information about Alkacon Software, please see the 022 * company website: http://www.alkacon.com 023 * 024 * For further information about OpenCms, please see the 025 * project website: http://www.opencms.org 026 * 027 * You should have received a copy of the GNU Lesser General Public 028 * License along with this library; if not, write to the Free Software 029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 030 */ 031 032package org.opencms.ade.containerpage.inherited; 033 034import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_CONFIGURATION; 035import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_ELEMENT; 036import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_HIDDEN; 037import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_KEY; 038import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_NAME; 039import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_NEWELEMENT; 040import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_ORDERKEY; 041import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_URI; 042import static org.opencms.ade.containerpage.inherited.CmsContainerConfiguration.N_VISIBLE; 043 044import org.opencms.ade.containerpage.shared.CmsInheritanceInfo; 045import org.opencms.file.CmsFile; 046import org.opencms.file.CmsObject; 047import org.opencms.file.CmsResource; 048import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 049import org.opencms.file.types.CmsResourceTypeXmlContent; 050import org.opencms.lock.CmsLock; 051import org.opencms.main.CmsException; 052import org.opencms.main.CmsLog; 053import org.opencms.main.OpenCms; 054import org.opencms.relations.CmsRelationType; 055import org.opencms.util.CmsStringUtil; 056import org.opencms.util.CmsUUID; 057import org.opencms.xml.CmsXmlUtils; 058import org.opencms.xml.containerpage.CmsContainerElementBean; 059import org.opencms.xml.content.CmsXmlContent; 060import org.opencms.xml.content.CmsXmlContentFactory; 061import org.opencms.xml.content.CmsXmlContentProperty; 062import org.opencms.xml.content.CmsXmlContentPropertyHelper; 063import org.opencms.xml.types.CmsXmlVfsFileValue; 064import org.opencms.xml.types.I_CmsXmlContentValue; 065 066import java.util.ArrayList; 067import java.util.HashMap; 068import java.util.List; 069import java.util.Locale; 070import java.util.Map; 071import java.util.Set; 072 073import org.apache.commons.logging.Log; 074 075import org.dom4j.Element; 076 077/** 078 * A helper class for writing inherited container configuration back to a VFS file.<p> 079 */ 080public class CmsContainerConfigurationWriter { 081 082 /** The logger instance for this class. */ 083 @SuppressWarnings("unused") 084 private static final Log LOG = CmsLog.getLog(CmsContainerConfigurationWriter.class); 085 086 /** 087 * Saves a list of container element beans to a file in the VFS.<p> 088 * 089 * @param cms the current CMS context 090 * @param name the name of the configuration to save 091 * @param newOrdering true if a new ordering needs to be saved 092 * @param pageResource a container page or folder 093 * @param elements the elements whose data should be saved 094 * 095 * @throws CmsException if something goes wrong 096 */ 097 public void save( 098 CmsObject cms, 099 String name, 100 boolean newOrdering, 101 CmsResource pageResource, 102 List<CmsContainerElementBean> elements) 103 throws CmsException { 104 105 cms = OpenCms.initCmsObject(cms); 106 cms.getRequestContext().setSiteRoot(""); 107 String configPath; 108 if (pageResource.isFolder()) { 109 configPath = CmsStringUtil.joinPaths( 110 pageResource.getRootPath(), 111 CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME); 112 } else { 113 configPath = CmsStringUtil.joinPaths( 114 CmsResource.getParentFolder(pageResource.getRootPath()), 115 CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME); 116 } 117 CmsInheritedContainerState state = OpenCms.getADEManager().getInheritedContainerState( 118 cms, 119 CmsResource.getParentFolder(CmsResource.getParentFolder(configPath)), 120 name); 121 Set<String> keys = state.getNewElementKeys(); 122 123 CmsResource configRes = null; 124 boolean needToUnlock = false; 125 if (!cms.existsResource(configPath)) { 126 // create it 127 configRes = cms.createResource( 128 configPath, 129 OpenCms.getResourceManager().getResourceType( 130 CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME)); 131 needToUnlock = true; 132 } 133 if (configRes == null) { 134 configRes = cms.readResource(configPath); 135 } 136 CmsFile configFile = cms.readFile(configRes); 137 // make sure the internal flag is set 138 configFile.setFlags(configFile.getFlags() | CmsResource.FLAG_INTERNAL); 139 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, configFile); 140 for (Locale localeToRemoveEntryFrom : content.getLocales()) { 141 removeExistingEntry(cms, content, localeToRemoveEntryFrom, name); 142 } 143 CmsContainerConfiguration configuration = createConfigurationBean(newOrdering, elements, keys); 144 145 Locale saveLocale = Locale.ENGLISH; 146 for (Locale locale : content.getLocales()) { 147 if (!saveLocale.equals(locale)) { 148 content.removeLocale(locale); 149 } 150 } 151 if (!content.hasLocale(saveLocale)) { 152 content.addLocale(cms, saveLocale); 153 } 154 Element parentElement = content.getLocaleNode(saveLocale); 155 serializeSingleConfiguration(cms, name, configuration, parentElement); 156 byte[] contentBytes = content.marshal(); 157 configFile.setContents(contentBytes); 158 CmsLock prevLock = cms.getLock(configRes); 159 boolean alreadyLocked = prevLock.isOwnedBy(cms.getRequestContext().getCurrentUser()); 160 if (!alreadyLocked) { 161 cms.lockResourceTemporary(configRes); 162 needToUnlock = true; 163 } 164 try { 165 cms.writeFile(configFile); 166 } finally { 167 if (needToUnlock) { 168 cms.unlockResource(configRes); 169 } 170 } 171 } 172 173 /** 174 * Serializes a single container configuration into an XML element.<p> 175 * 176 * @param cms the current CMS context 177 * @param name the configuration name 178 * @param config the configuration bean 179 * @param parentElement the parent element to which the new element should be attached 180 * @return the created XML element 181 * 182 * @throws CmsException if something goes wrong 183 */ 184 public Element serializeSingleConfiguration( 185 CmsObject cms, 186 String name, 187 CmsContainerConfiguration config, 188 Element parentElement) 189 throws CmsException { 190 191 List<String> visibles = new ArrayList<String>(); 192 List<String> invisibles = new ArrayList<String>(); 193 for (String key : config.getVisibility().keySet()) { 194 Boolean value = config.getVisibility().get(key); 195 if (value.booleanValue()) { 196 visibles.add(key); 197 } else { 198 invisibles.add(key); 199 } 200 } 201 if (config.getOrdering().isEmpty() 202 && visibles.isEmpty() 203 && invisibles.isEmpty() 204 && config.getNewElements().isEmpty()) { 205 // don't add empty inheritance configurations 206 return null; 207 } 208 Element root = parentElement.addElement(N_CONFIGURATION); 209 root.addElement(N_NAME).addCDATA(name); 210 for (String orderKey : config.getOrdering()) { 211 root.addElement(N_ORDERKEY).addCDATA(orderKey); 212 } 213 for (String visible : visibles) { 214 root.addElement(N_VISIBLE).addCDATA(visible); 215 } 216 for (String invisible : invisibles) { 217 root.addElement(N_HIDDEN).addCDATA(invisible); 218 } 219 for (Map.Entry<String, CmsContainerElementBean> entry : config.getNewElements().entrySet()) { 220 String key = entry.getKey(); 221 CmsContainerElementBean elementBean = entry.getValue(); 222 223 elementBean.initResource(cms); 224 Map<String, CmsXmlContentProperty> settingConfiguration = getSettingConfiguration( 225 cms, 226 elementBean.getResource()); 227 CmsUUID structureId = elementBean.getId(); 228 Map<String, String> settings = elementBean.getIndividualSettings(); 229 Element newElementElement = root.addElement(N_NEWELEMENT); 230 newElementElement.addElement(N_KEY).addCDATA(key); 231 Element elementElement = newElementElement.addElement(N_ELEMENT); 232 Element uriElement = elementElement.addElement(N_URI); 233 CmsXmlVfsFileValue.fillEntry(uriElement, structureId, "", CmsRelationType.XML_STRONG); 234 CmsXmlContentPropertyHelper.saveProperties(cms, elementElement, settings, settingConfiguration, true); 235 } 236 return root; 237 } 238 239 /** 240 * Converts a list of container elements into a bean which should be saved to the inherited container configuration.<p> 241 * 242 * @param newOrdering if true, save a new ordering 243 * @param elements the elements which should be converted 244 * @param parentKeys the keys for new elements defined in the parent configurations 245 * 246 * @return the bean containing the information from the container elements which should be saved 247 */ 248 protected CmsContainerConfiguration createConfigurationBean( 249 boolean newOrdering, 250 List<CmsContainerElementBean> elements, 251 Set<String> parentKeys) { 252 253 Map<String, CmsContainerElementBean> newElements = new HashMap<String, CmsContainerElementBean>(); 254 List<String> ordering = new ArrayList<String>(); 255 Map<String, Boolean> visibility = new HashMap<String, Boolean>(); 256 for (CmsContainerElementBean elementBean : elements) { 257 CmsInheritanceInfo info = elementBean.getInheritanceInfo(); 258 if (info.isNew()) { 259 newElements.put(info.getKey(), elementBean); 260 } 261 } 262 if (newOrdering) { 263 for (CmsContainerElementBean elementBean : elements) { 264 CmsInheritanceInfo info = elementBean.getInheritanceInfo(); 265 // remove dangling element references 266 if (parentKeys.contains(info.getKey()) || newElements.containsKey(info.getKey())) { 267 ordering.add(info.getKey()); 268 } 269 } 270 } 271 for (CmsContainerElementBean elementBean : elements) { 272 CmsInheritanceInfo info = elementBean.getInheritanceInfo(); 273 if (info.isVisible() != info.isParentVisible()) { 274 visibility.put(info.getKey(), Boolean.valueOf(info.isVisible())); 275 } 276 } 277 278 CmsContainerConfiguration configuration = new CmsContainerConfiguration(ordering, visibility, newElements); 279 return configuration; 280 } 281 282 /** 283 * Gets the setting configuration of an element.<p> 284 * 285 * @param cms the current CMS context 286 * @param resource the resource for which the setting configuration should be returned 287 * @return the setting configuration for that element 288 * 289 * @throws CmsException if something goes wrong 290 */ 291 protected Map<String, CmsXmlContentProperty> getSettingConfiguration(CmsObject cms, CmsResource resource) 292 throws CmsException { 293 294 return OpenCms.getADEManager().getElementSettings(cms, resource); 295 } 296 297 /** 298 * Removes an existing inheritance container entry with a given name from the configuration file.<p> 299 * 300 * This does nothing if no such entry actually exists.<p> 301 * 302 * @param cms the current CMS context 303 * @param content the XML content 304 * @param locale the locale from which to remove the entry 305 * @param name the name of the entry 306 * 307 */ 308 protected void removeExistingEntry(CmsObject cms, CmsXmlContent content, Locale locale, String name) { 309 310 if (!content.hasLocale(locale)) { 311 return; 312 } 313 String entriesXpath = N_CONFIGURATION; 314 List<I_CmsXmlContentValue> values = content.getValues(entriesXpath, locale); 315 int valueIndex = 0; 316 for (I_CmsXmlContentValue value : values) { 317 String valueXpath = value.getPath(); 318 I_CmsXmlContentValue nameValue = content.getValue(CmsXmlUtils.concatXpath(valueXpath, N_NAME), locale); 319 String currentName = nameValue.getStringValue(cms); 320 if (currentName.equals(name)) { 321 content.removeValue(valueXpath, locale, valueIndex); 322 break; 323 } 324 valueIndex += 1; 325 } 326 } 327 328 /** 329 * Saves a single container configuration in an XML content object, but doesn't write it to the VFS.<p> 330 * 331 * If the XML content passed as a parameter is null, a new XML content object will be created 332 * 333 * @param cms the current CMS context 334 * @param content the XML content 335 * @param locale the locale in which the configuration should be written 336 * @param name the name of the configuration 337 * @param configuration the configuration to write 338 * 339 * @return the modified or new XML content 340 * 341 * @throws CmsException if something goes wrong 342 */ 343 protected CmsXmlContent saveInContentObject( 344 CmsObject cms, 345 CmsXmlContent content, 346 Locale locale, 347 String name, 348 CmsContainerConfiguration configuration) 349 throws CmsException { 350 351 if (content == null) { 352 content = CmsXmlContentFactory.createDocument( 353 cms, 354 locale, 355 (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType( 356 CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME)); 357 } 358 359 if (!content.hasLocale(locale)) { 360 content.addLocale(cms, locale); 361 } 362 Element parentElement = content.getLocaleNode(locale); 363 serializeSingleConfiguration(cms, name, configuration, parentElement); 364 return content; 365 } 366 367}