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) throws CmsException { 103 104 cms = OpenCms.initCmsObject(cms); 105 cms.getRequestContext().setSiteRoot(""); 106 String configPath; 107 if (pageResource.isFolder()) { 108 configPath = CmsStringUtil.joinPaths( 109 pageResource.getRootPath(), 110 CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME); 111 } else { 112 configPath = CmsStringUtil.joinPaths( 113 CmsResource.getParentFolder(pageResource.getRootPath()), 114 CmsContainerConfigurationCache.INHERITANCE_CONFIG_FILE_NAME); 115 } 116 CmsInheritedContainerState state = OpenCms.getADEManager().getInheritedContainerState( 117 cms, 118 CmsResource.getParentFolder(CmsResource.getParentFolder(configPath)), 119 name); 120 Set<String> keys = state.getNewElementKeys(); 121 122 CmsResource configRes = null; 123 boolean needToUnlock = false; 124 if (!cms.existsResource(configPath)) { 125 // create it 126 configRes = cms.createResource( 127 configPath, 128 OpenCms.getResourceManager().getResourceType( 129 CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME)); 130 needToUnlock = true; 131 } 132 if (configRes == null) { 133 configRes = cms.readResource(configPath); 134 } 135 CmsFile configFile = cms.readFile(configRes); 136 // make sure the internal flag is set 137 configFile.setFlags(configFile.getFlags() | CmsResource.FLAG_INTERNAL); 138 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, configFile); 139 for (Locale localeToRemoveEntryFrom : content.getLocales()) { 140 removeExistingEntry(cms, content, localeToRemoveEntryFrom, name); 141 } 142 CmsContainerConfiguration configuration = createConfigurationBean(newOrdering, elements, keys); 143 144 Locale saveLocale = Locale.ENGLISH; 145 for (Locale locale : content.getLocales()) { 146 if (!saveLocale.equals(locale)) { 147 content.removeLocale(locale); 148 } 149 } 150 if (!content.hasLocale(saveLocale)) { 151 content.addLocale(cms, saveLocale); 152 } 153 Element parentElement = content.getLocaleNode(saveLocale); 154 serializeSingleConfiguration(cms, name, configuration, parentElement); 155 byte[] contentBytes = content.marshal(); 156 configFile.setContents(contentBytes); 157 CmsLock prevLock = cms.getLock(configRes); 158 boolean alreadyLocked = prevLock.isOwnedBy(cms.getRequestContext().getCurrentUser()); 159 if (!alreadyLocked) { 160 cms.lockResourceTemporary(configRes); 161 needToUnlock = true; 162 } 163 try { 164 cms.writeFile(configFile); 165 } finally { 166 if (needToUnlock) { 167 cms.unlockResource(configRes); 168 } 169 } 170 } 171 172 /** 173 * Serializes a single container configuration into an XML element.<p> 174 * 175 * @param cms the current CMS context 176 * @param name the configuration name 177 * @param config the configuration bean 178 * @param parentElement the parent element to which the new element should be attached 179 * @return the created XML element 180 * 181 * @throws CmsException if something goes wrong 182 */ 183 public Element serializeSingleConfiguration( 184 CmsObject cms, 185 String name, 186 CmsContainerConfiguration config, 187 Element parentElement) throws CmsException { 188 189 List<String> visibles = new ArrayList<String>(); 190 List<String> invisibles = new ArrayList<String>(); 191 for (String key : config.getVisibility().keySet()) { 192 Boolean value = config.getVisibility().get(key); 193 if (value.booleanValue()) { 194 visibles.add(key); 195 } else { 196 invisibles.add(key); 197 } 198 } 199 if (config.getOrdering().isEmpty() 200 && visibles.isEmpty() 201 && invisibles.isEmpty() 202 && config.getNewElements().isEmpty()) { 203 // don't add empty inheritance configurations 204 return null; 205 } 206 Element root = parentElement.addElement(N_CONFIGURATION); 207 root.addElement(N_NAME).addCDATA(name); 208 for (String orderKey : config.getOrdering()) { 209 root.addElement(N_ORDERKEY).addCDATA(orderKey); 210 } 211 for (String visible : visibles) { 212 root.addElement(N_VISIBLE).addCDATA(visible); 213 } 214 for (String invisible : invisibles) { 215 root.addElement(N_HIDDEN).addCDATA(invisible); 216 } 217 for (Map.Entry<String, CmsContainerElementBean> entry : config.getNewElements().entrySet()) { 218 String key = entry.getKey(); 219 CmsContainerElementBean elementBean = entry.getValue(); 220 221 elementBean.initResource(cms); 222 Map<String, CmsXmlContentProperty> settingConfiguration = getSettingConfiguration( 223 cms, 224 elementBean.getResource()); 225 CmsUUID structureId = elementBean.getId(); 226 Map<String, String> settings = elementBean.getIndividualSettings(); 227 Element newElementElement = root.addElement(N_NEWELEMENT); 228 newElementElement.addElement(N_KEY).addCDATA(key); 229 Element elementElement = newElementElement.addElement(N_ELEMENT); 230 Element uriElement = elementElement.addElement(N_URI); 231 CmsXmlVfsFileValue.fillEntry(uriElement, structureId, "", CmsRelationType.XML_STRONG); 232 CmsXmlContentPropertyHelper.saveProperties(cms, elementElement, settings, settingConfiguration); 233 } 234 return root; 235 } 236 237 /** 238 * Converts a list of container elements into a bean which should be saved to the inherited container configuration.<p> 239 * 240 * @param newOrdering if true, save a new ordering 241 * @param elements the elements which should be converted 242 * @param parentKeys the keys for new elements defined in the parent configurations 243 * 244 * @return the bean containing the information from the container elements which should be saved 245 */ 246 protected CmsContainerConfiguration createConfigurationBean( 247 boolean newOrdering, 248 List<CmsContainerElementBean> elements, 249 Set<String> parentKeys) { 250 251 Map<String, CmsContainerElementBean> newElements = new HashMap<String, CmsContainerElementBean>(); 252 List<String> ordering = new ArrayList<String>(); 253 Map<String, Boolean> visibility = new HashMap<String, Boolean>(); 254 for (CmsContainerElementBean elementBean : elements) { 255 CmsInheritanceInfo info = elementBean.getInheritanceInfo(); 256 if (info.isNew()) { 257 newElements.put(info.getKey(), elementBean); 258 } 259 } 260 if (newOrdering) { 261 for (CmsContainerElementBean elementBean : elements) { 262 CmsInheritanceInfo info = elementBean.getInheritanceInfo(); 263 // remove dangling element references 264 if (parentKeys.contains(info.getKey()) || newElements.containsKey(info.getKey())) { 265 ordering.add(info.getKey()); 266 } 267 } 268 } 269 for (CmsContainerElementBean elementBean : elements) { 270 CmsInheritanceInfo info = elementBean.getInheritanceInfo(); 271 if (info.isVisible() != info.isParentVisible()) { 272 visibility.put(info.getKey(), new Boolean(info.isVisible())); 273 } 274 } 275 276 CmsContainerConfiguration configuration = new CmsContainerConfiguration(ordering, visibility, newElements); 277 return configuration; 278 } 279 280 /** 281 * Gets the setting configuration of an element.<p> 282 * 283 * @param cms the current CMS context 284 * @param resource the resource for which the setting configuration should be returned 285 * @return the setting configuration for that element 286 * 287 * @throws CmsException if something goes wrong 288 */ 289 protected Map<String, CmsXmlContentProperty> getSettingConfiguration(CmsObject cms, CmsResource resource) 290 throws CmsException { 291 292 return OpenCms.getADEManager().getElementSettings(cms, resource); 293 } 294 295 /** 296 * Removes an existing inheritance container entry with a given name from the configuration file.<p> 297 * 298 * This does nothing if no such entry actually exists.<p> 299 * 300 * @param cms the current CMS context 301 * @param content the XML content 302 * @param locale the locale from which to remove the entry 303 * @param name the name of the entry 304 * 305 */ 306 protected void removeExistingEntry(CmsObject cms, CmsXmlContent content, Locale locale, String name) { 307 308 if (!content.hasLocale(locale)) { 309 return; 310 } 311 String entriesXpath = N_CONFIGURATION; 312 List<I_CmsXmlContentValue> values = content.getValues(entriesXpath, locale); 313 int valueIndex = 0; 314 for (I_CmsXmlContentValue value : values) { 315 String valueXpath = value.getPath(); 316 I_CmsXmlContentValue nameValue = content.getValue(CmsXmlUtils.concatXpath(valueXpath, N_NAME), locale); 317 String currentName = nameValue.getStringValue(cms); 318 if (currentName.equals(name)) { 319 content.removeValue(valueXpath, locale, valueIndex); 320 break; 321 } 322 valueIndex += 1; 323 } 324 } 325 326 /** 327 * Saves a single container configuration in an XML content object, but doesn't write it to the VFS.<p> 328 * 329 * If the XML content passed as a parameter is null, a new XML content object will be created 330 * 331 * @param cms the current CMS context 332 * @param content the XML content 333 * @param locale the locale in which the configuration should be written 334 * @param name the name of the configuration 335 * @param configuration the configuration to write 336 * 337 * @return the modified or new XML content 338 * 339 * @throws CmsException if something goes wrong 340 */ 341 protected CmsXmlContent saveInContentObject( 342 CmsObject cms, 343 CmsXmlContent content, 344 Locale locale, 345 String name, 346 CmsContainerConfiguration configuration) throws CmsException { 347 348 if (content == null) { 349 content = CmsXmlContentFactory.createDocument( 350 cms, 351 locale, 352 (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType( 353 CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_CONFIG_TYPE_NAME)); 354 } 355 356 if (!content.hasLocale(locale)) { 357 content.addLocale(cms, locale); 358 } 359 Element parentElement = content.getLocaleNode(locale); 360 serializeSingleConfiguration(cms, name, configuration, parentElement); 361 return content; 362 } 363 364}