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.search.galleries; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsProperty; 032import org.opencms.file.CmsPropertyDefinition; 033import org.opencms.file.CmsResource; 034import org.opencms.file.CmsResourceFilter; 035import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 036import org.opencms.i18n.CmsMultiMessages; 037import org.opencms.jsp.util.CmsJspContentAccessBean; 038import org.opencms.jsp.util.CmsObjectFunctionTransformer; 039import org.opencms.jsp.util.CmsStringTemplateRenderer; 040import org.opencms.main.CmsException; 041import org.opencms.main.CmsLog; 042import org.opencms.main.OpenCms; 043import org.opencms.relations.CmsRelation; 044import org.opencms.relations.CmsRelationFilter; 045import org.opencms.util.CmsCollectionsGenericWrapper; 046import org.opencms.util.CmsMacroResolver; 047import org.opencms.xml.A_CmsXmlDocument; 048import org.opencms.xml.types.I_CmsXmlContentValue; 049 050import java.util.Collection; 051import java.util.List; 052import java.util.Locale; 053import java.util.Map; 054import java.util.function.Function; 055import java.util.regex.Matcher; 056import java.util.regex.Pattern; 057 058import org.apache.commons.logging.Log; 059 060import com.google.common.collect.Lists; 061import com.google.common.collect.Maps; 062 063/** 064 * Macro resolver used to resolve macros for the gallery name mapping.<p> 065 * 066 * This supports the following special macros: 067 * <ul> 068 * <li>%(no_prefix:some more text): This will expand to "some more text" if, after expanding all other macros in the input string, 069 * there is at least one character before the occurence of this macro, and to an empty string otherwise. 070 * <li>%(value:/Some/XPath): This will expand to the value under the given XPath in the XML content and locale with 071 * which the macro resolver was initialized. If no value is found under the XPath, the macro will expand to an empty string. 072 * <li>%(page_nav): This will expand to the NavText property of the container page in which this element is referenced. 073 * If this element is referenced from multiple container pages with the same locale, this macro is expanded 074 * to an empty string. 075 *<li>%(page_title): Same as %(page_nav), but uses the Title property instead of NavText. 076 *</ul> 077 */ 078public class CmsGalleryNameMacroResolver extends CmsMacroResolver { 079 080 /** The logger instance for the class. */ 081 private static final Log LOG = CmsLog.getLog(CmsGalleryNameMacroResolver.class); 082 083 /** Macro prefix. */ 084 public static final String PREFIX_VALUE = "value:"; 085 086 /** Macro name. */ 087 public static final String PAGE_TITLE = "page_title"; 088 089 /** Macro name. */ 090 public static final String PAGE_NAV = "page_nav"; 091 092 /** Macro prefix. */ 093 public static final String NO_PREFIX = "no_prefix"; 094 095 /** Pattern used to match the no_prefix macro. */ 096 public static final Pattern NO_PREFIX_PATTERN = Pattern.compile("%\\(" + NO_PREFIX + ":(.*?)\\)"); 097 098 /** Prefix for the stringtemplate macro. */ 099 public static final String PREFIX_STRINGTEMPLATE = "stringtemplate:"; 100 101 /** The XML content to use for the gallery name mapping. */ 102 private A_CmsXmlDocument m_content; 103 104 /** The locale in the XML content. */ 105 private Locale m_contentLocale; 106 107 /** The default string template source. */ 108 private final Function<String, String> m_defaultStringTemplateSource = s -> { 109 return m_content.getHandler().getParameter(s); 110 }; 111 112 /** The current string template source. */ 113 private Function<String, String> m_stringTemplateSource = m_defaultStringTemplateSource; 114 115 /** 116 * Creates a new instance.<p> 117 * 118 * @param cms the CMS context to use for VFS operations 119 * @param content the content to use for macro value lookup 120 * @param locale the locale to use for macro value lookup 121 */ 122 public CmsGalleryNameMacroResolver(CmsObject cms, A_CmsXmlDocument content, Locale locale) { 123 124 setCmsObject(cms); 125 CmsMultiMessages message = new CmsMultiMessages(locale); 126 message.addMessages(OpenCms.getWorkplaceManager().getMessages(locale)); 127 message.addMessages(content.getContentDefinition().getContentHandler().getMessages(locale)); 128 setMessages(message); 129 m_content = content; 130 m_contentLocale = locale; 131 } 132 133 /** 134 * @see org.opencms.util.CmsMacroResolver#getMacroValue(java.lang.String) 135 */ 136 @Override 137 public String getMacroValue(String macro) { 138 139 if (macro.startsWith(PREFIX_VALUE)) { 140 String path = macro.substring(PREFIX_VALUE.length()); 141 I_CmsXmlContentValue contentValue = m_content.getValue(path, m_contentLocale); 142 String value = null; 143 if (contentValue != null) { 144 value = contentValue.getStringValue(m_cms); 145 } 146 if (value == null) { 147 value = ""; 148 } 149 return value; 150 } else if (macro.equals(PAGE_TITLE)) { 151 return getContainerPageProperty(CmsPropertyDefinition.PROPERTY_TITLE); 152 } else if (macro.equals(PAGE_NAV)) { 153 return getContainerPageProperty(CmsPropertyDefinition.PROPERTY_NAVTEXT); 154 } else if (macro.startsWith(PREFIX_STRINGTEMPLATE)) { 155 return resolveStringTemplate(macro.substring(PREFIX_STRINGTEMPLATE.length())); 156 } else if (macro.startsWith(NO_PREFIX)) { 157 return "%(" + macro + ")"; 158 // this is just to prevent the %(no_prefix:...) macro from being expanded to an empty string. We could call setKeepEmptyMacros(true) instead, 159 // but that would also affect other macros. 160 } else { 161 return super.getMacroValue(macro); 162 } 163 } 164 165 /** 166 * @see org.opencms.util.CmsMacroResolver#resolveMacros(java.lang.String) 167 */ 168 @Override 169 public String resolveMacros(String input) { 170 171 if (input == null) { 172 return null; 173 } 174 // We are overriding this method to implement the no_prefix macro. This is because 175 // we only know what the no_prefix macro should expand to after resolving all other 176 // macros (there could be an arbitrary number of macros before it which might potentially 177 // all expand to the empty string). 178 String result = super.resolveMacros(input); 179 Matcher matcher = NO_PREFIX_PATTERN.matcher(result); 180 if (matcher.find()) { 181 StringBuffer resultBuffer = new StringBuffer(); 182 matcher.appendReplacement( 183 resultBuffer, 184 matcher.start() == 0 ? "" : result.substring(matcher.start(1), matcher.end(1))); 185 matcher.appendTail(resultBuffer); 186 result = resultBuffer.toString(); 187 } 188 return result; 189 } 190 191 public void setStringTemplateSource(Function<String, String> stringtemplateSource) { 192 193 if (stringtemplateSource == null) { 194 stringtemplateSource = m_defaultStringTemplateSource; 195 } 196 m_stringTemplateSource = stringtemplateSource; 197 } 198 199 /** 200 * Gets the given property of the container page referencing this content.<p> 201 * 202 * If more than one container page with the same locale reference this content, the empty string will be returned. 203 * 204 * @param propName the property name to look up 205 * 206 * @return the value of the named property on the container page, or an empty string 207 */ 208 protected String getContainerPageProperty(String propName) { 209 210 try { 211 Collection<CmsRelation> relations = m_cms.readRelations( 212 CmsRelationFilter.relationsToStructureId(m_content.getFile().getStructureId())); 213 Map<Locale, String> pagePropsByLocale = Maps.newHashMap(); 214 for (CmsRelation relation : relations) { 215 CmsResource source = relation.getSource(m_cms, CmsResourceFilter.IGNORE_EXPIRATION); 216 if (CmsResourceTypeXmlContainerPage.isContainerPage(source)) { 217 List<CmsProperty> pagePropertiesList = m_cms.readPropertyObjects(source, true); 218 Map<String, CmsProperty> pageProperties = CmsProperty.toObjectMap(pagePropertiesList); 219 Locale pageLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, source); 220 CmsProperty pagePropCandidate = pageProperties.get(propName); 221 if (pagePropCandidate != null) { 222 if (pagePropsByLocale.get(pageLocale) == null) { 223 pagePropsByLocale.put(pageLocale, pagePropCandidate.getValue()); 224 } else { 225 return ""; // more than one container page per locale is referencing this content. 226 } 227 } 228 } 229 } 230 Locale matchingLocale = OpenCms.getLocaleManager().getBestMatchingLocale( 231 m_contentLocale, 232 OpenCms.getLocaleManager().getDefaultLocales(), 233 Lists.newArrayList(pagePropsByLocale.keySet())); 234 String result = pagePropsByLocale.get(matchingLocale); 235 if (result == null) { 236 result = ""; 237 } 238 return result; 239 } catch (CmsException e) { 240 LOG.warn(e.getLocalizedMessage(), e); 241 return null; 242 } 243 } 244 245 /** 246 * Evaluates the contents of a %(stringtemplate:...) macro by evaluating them as StringTemplate code.<p> 247 * 248 * @param stMacro the contents of the macro after the stringtemplate: prefix 249 * @return the StringTemplate evaluation result 250 */ 251 private String resolveStringTemplate(String stMacro) { 252 253 String template = m_stringTemplateSource.apply(stMacro.trim()); 254 if (template == null) { 255 return ""; 256 } 257 CmsJspContentAccessBean jspContentAccess = new CmsJspContentAccessBean(m_cms, m_contentLocale, m_content); 258 Map<String, Object> params = Maps.newHashMap(); 259 params.put( 260 CmsStringTemplateRenderer.KEY_FUNCTIONS, 261 CmsCollectionsGenericWrapper.createLazyMap(new CmsObjectFunctionTransformer(m_cms))); 262 263 // We don't necessarily need the page title / navigation, so instead of passing the computed values to the template, we pass objects whose 264 // toString methods compute the values 265 params.put(PAGE_TITLE, new Object() { 266 267 @Override 268 public String toString() { 269 270 return getContainerPageProperty(CmsPropertyDefinition.PROPERTY_TITLE); 271 } 272 }); 273 274 params.put(PAGE_NAV, new Object() { 275 276 @Override 277 public String toString() { 278 279 return getContainerPageProperty(CmsPropertyDefinition.PROPERTY_NAVTEXT); 280 281 } 282 }); 283 String result = CmsStringTemplateRenderer.renderTemplate(m_cms, template, jspContentAccess, params); 284 return result; 285 } 286}