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, 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.CmsProperty; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResourceFilter; 036import org.opencms.i18n.CmsLocaleManager; 037import org.opencms.loader.I_CmsFileNameGenerator; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.main.OpenCms; 041import org.opencms.util.CmsStringUtil; 042import org.opencms.util.CmsUUID; 043 044import java.util.HashMap; 045import java.util.Iterator; 046import java.util.List; 047import java.util.Locale; 048import java.util.Map; 049import java.util.Set; 050 051import org.apache.commons.lang3.RandomStringUtils; 052import org.apache.commons.logging.Log; 053 054import com.google.common.collect.Lists; 055import com.google.common.collect.Sets; 056 057/** 058 * A class which represents the context for resolving all content value mappings of an XML content.<p> 059 * 060 * Since the content handler instance is shared between all contents of the same XML content type, we can't use 061 * it to store data which is only relevant for resolving the mappings of a single XML content, so this class was created. 062 */ 063public class CmsMappingResolutionContext { 064 065 /** 066 * The attribute type. 067 */ 068 public enum AttributeType { 069 expiration, release; 070 } 071 072 /** 073 * Internal bean used to keep track of URL name mappings.<p> 074 */ 075 class InternalUrlNameMappingEntry { 076 077 /** Locale of the mapping. */ 078 private Locale m_locale; 079 080 /** URL name of the mapping. */ 081 private String m_name; 082 083 /** Structure ID of the mapping. */ 084 private CmsUUID m_structureId; 085 086 /** 087 * Creates a new instance.<p> 088 * 089 * @param structureId the structure id 090 * @param name the URL name 091 * @param locale the locale 092 */ 093 public InternalUrlNameMappingEntry(CmsUUID structureId, String name, Locale locale) { 094 095 m_name = name; 096 m_structureId = structureId; 097 m_locale = locale; 098 } 099 100 /** 101 * Returns the locale.<p> 102 * 103 * @return the locale 104 */ 105 public Locale getLocale() { 106 107 return m_locale; 108 } 109 110 /** 111 * Returns the name.<p> 112 * 113 * @return the name 114 */ 115 public String getName() { 116 117 return m_name; 118 } 119 120 /** 121 * Returns the structureId.<p> 122 * 123 * @return the structureId 124 */ 125 public CmsUUID getStructureId() { 126 127 return m_structureId; 128 } 129 130 } 131 132 /** The log instance for this class. */ 133 private static final Log LOG = CmsLog.getLog(CmsMappingResolutionContext.class); 134 135 /** The CMS context to use. */ 136 private CmsObject m_cms; 137 138 /** The content being processed. */ 139 private CmsXmlContent m_content; 140 141 /** Stored expiration dates. */ 142 private Map<Locale, Long> m_dateExpired = new HashMap<>(); 143 144 /** Stored release dates. */ 145 private Map<Locale, Long> m_dateReleased = new HashMap<>(); 146 147 /** True if the schema for the content has attribute mappings. */ 148 private boolean m_hasAttributeMappings; 149 150 /** The list of URL name mappings. */ 151 private List<InternalUrlNameMappingEntry> m_urlNameMappingEntries = Lists.newArrayList(); 152 153 /** 154 * Creates a new instance.<p> 155 * @param content the xml content 156 * @param hasAttributeMappings true if the schema has attribute mappings 157 */ 158 public CmsMappingResolutionContext(CmsXmlContent content, boolean hasAttributeMappings) { 159 160 m_content = content; 161 m_hasAttributeMappings = hasAttributeMappings; 162 } 163 164 /** 165 * Writes all the stored URL name mappings to the database.<p> 166 * 167 * @throws CmsException if something goes wrong 168 */ 169 public void commitUrlNameMappings() throws CmsException { 170 171 Set<CmsUUID> structureIds = Sets.newHashSet(); 172 for (InternalUrlNameMappingEntry entry : m_urlNameMappingEntries) { 173 structureIds.add(entry.getStructureId()); 174 } 175 176 boolean urlnameReplace = false; 177 for (CmsUUID structureId : structureIds) { 178 try { 179 CmsResource resource = m_cms.readResource(structureId, CmsResourceFilter.ALL); 180 CmsProperty prop = m_cms.readPropertyObject( 181 resource, 182 CmsPropertyDefinition.PROPERTY_URLNAME_REPLACE, 183 true); 184 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(prop.getValue())) { 185 urlnameReplace = Boolean.parseBoolean(prop.getValue()); 186 } 187 } catch (CmsException e) { 188 LOG.error("Error while trying to read urlname.replace: " + e.getLocalizedMessage(), e); 189 } 190 191 } 192 193 I_CmsFileNameGenerator nameGen = OpenCms.getResourceManager().getNameGenerator(); 194 for (InternalUrlNameMappingEntry entry : m_urlNameMappingEntries) { 195 Iterator<String> nameSeq = nameGen.getUrlNameSequence(entry.getName()); 196 m_cms.writeUrlNameMapping(nameSeq, entry.getStructureId(), entry.getLocale().toString(), urlnameReplace); 197 } 198 199 } 200 201 /** 202 * Finalizes the mappings.<p> 203 * 204 * @throws CmsException if something goes wrong 205 */ 206 public void finalizeMappings() throws CmsException { 207 208 commitUrlNameMappings(); 209 if (m_hasAttributeMappings) { 210 // we do not want to change manually set attributes in case there are no attribute mappings 211 writeAttributes(); 212 } 213 } 214 215 /** 216 * Stores the mapped expiration date for the given locale. 217 * 218 * @param locale the locale 219 * @param expiration the expiration date 220 */ 221 public void putExpirationDate(Locale locale, long expiration) { 222 223 m_dateExpired.put(locale, Long.valueOf(expiration)); 224 } 225 226 /** 227 * Stores the mapped release date for the given locale. 228 * 229 * @param locale the locale 230 * @param release the release date 231 */ 232 public void putReleaseDate(Locale locale, long release) { 233 234 m_dateReleased.put(locale, Long.valueOf(release)); 235 } 236 237 /** 238 * Helper method for setting release/expiration date. 239 * 240 * <p>Needs to also set the attributes on the resource of m_content because it's written later by the content handler. 241 * 242 * @param res the resource to set 243 * @param type the attribute type 244 * @param value the value to set (null for default value) 245 * 246 * @throws CmsException if something goes wrong 247 */ 248 public void setAttribute(CmsResource res, AttributeType type, Long value) throws CmsException { 249 250 if (type == AttributeType.release) { 251 long actualValue = value != null ? value.longValue() : 0; 252 m_cms.setDateReleased(res, actualValue, false); 253 if ((m_content.getFile() != null) && res.getStructureId().equals(m_content.getFile().getStructureId())) { 254 m_content.getFile().setDateReleased(actualValue); 255 } 256 } else if (type == AttributeType.expiration) { 257 long actualValue = value != null ? value.longValue() : Long.MAX_VALUE; 258 m_cms.setDateExpired(res, actualValue, false); 259 if ((m_content.getFile() != null) && res.getStructureId().equals(m_content.getFile().getStructureId())) { 260 m_content.getFile().setDateExpired(actualValue); 261 } 262 } 263 } 264 265 /** 266 * Sets the CMS context to use.<p> 267 * 268 * @param cms the CMS context 269 */ 270 public void setCmsObject(CmsObject cms) { 271 272 m_cms = cms; 273 } 274 275 /** 276 * Writes the mapped attributes. 277 */ 278 protected void writeAttributes() { 279 280 String p = "[" + RandomStringUtils.randomAlphanumeric(6) + "] "; 281 CmsFile file = m_content.getFile(); 282 LOG.info(p + "Processing attributes for " + file.getRootPath()); 283 try { 284 for (CmsResource sibling : m_cms.readSiblings(file, CmsResourceFilter.IGNORE_EXPIRATION)) { 285 LOG.info(p + "Processing sibling " + sibling.getRootPath()); 286 try { 287 CmsProperty localeProp = m_cms.readPropertyObject( 288 sibling, 289 CmsPropertyDefinition.PROPERTY_LOCALE, 290 true); 291 List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(); 292 if (!localeProp.isNullProperty()) { 293 String localeStr = localeProp.getValue(); 294 List<Locale> tempLocales = CmsLocaleManager.getLocales(localeStr); 295 if (!tempLocales.isEmpty()) { 296 locales = tempLocales; 297 } 298 } 299 LOG.info(p + "Using locale precedence " + locales); 300 boolean foundLocale = false; 301 for (Locale locale : locales) { 302 // Use first locale from the property which is actually in the content, whether it has the values or not 303 // (if it doesn't have them, i.e. null is passed to setAttribute, the default values wlll be set) 304 if (m_content.hasLocale(locale)) { 305 LOG.info(p + "Mapping attributes from locale " + locale); 306 setAttribute(sibling, AttributeType.release, m_dateReleased.get(locale)); 307 setAttribute(sibling, AttributeType.expiration, m_dateExpired.get(locale)); 308 foundLocale = true; 309 break; 310 } 311 } 312 if (!foundLocale) { 313 LOG.info(p + "No mapping locale found, resetting attributes"); 314 setAttribute(sibling, AttributeType.release, null); 315 setAttribute(sibling, AttributeType.expiration, null); 316 } 317 } catch (CmsException e) { 318 LOG.error(p + e.getLocalizedMessage(), e); 319 } 320 } 321 322 } catch (Exception e) { 323 LOG.error(p + e.getLocalizedMessage(), e); 324 } 325 } 326 327 /** 328 * Adds an URL name mapping which should be written to the database later.<p> 329 * 330 * @param name the mapping name 331 * @param locale the locale 332 * @param structureId the structure ID 333 */ 334 void addUrlNameMapping(String name, Locale locale, CmsUUID structureId) { 335 336 m_urlNameMappingEntries.add(new InternalUrlNameMappingEntry(structureId, name, locale)); 337 } 338}