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 GmbH & Co. KG, 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.util; 029 030import org.opencms.db.CmsUserSettings; 031import org.opencms.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsProperty; 034import org.opencms.file.CmsPropertyDefinition; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.types.CmsResourceTypeBinary; 038import org.opencms.file.types.CmsResourceTypeImage; 039import org.opencms.flex.CmsFlexController; 040import org.opencms.i18n.CmsMessageContainer; 041import org.opencms.i18n.CmsMessages; 042import org.opencms.i18n.CmsMultiMessages; 043import org.opencms.main.CmsException; 044import org.opencms.main.CmsIllegalArgumentException; 045import org.opencms.main.CmsLog; 046import org.opencms.main.OpenCms; 047import org.opencms.report.I_CmsReport; 048import org.opencms.security.CmsOrganizationalUnit; 049import org.opencms.ui.apps.sitemanager.CmsSiteManager; 050import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.Descriptor; 051import org.opencms.xml.CmsXmlException; 052import org.opencms.xml.content.CmsXmlContent; 053import org.opencms.xml.content.CmsXmlContentFactory; 054import org.opencms.xml.content.CmsXmlContentValueSequence; 055 056import java.io.UnsupportedEncodingException; 057import java.nio.charset.Charset; 058import java.util.Arrays; 059import java.util.Collections; 060import java.util.HashMap; 061import java.util.Iterator; 062import java.util.LinkedHashMap; 063import java.util.List; 064import java.util.Map; 065import java.util.Properties; 066 067import javax.servlet.jsp.PageContext; 068 069import org.apache.commons.collections.Factory; 070import org.apache.commons.logging.Log; 071 072import com.google.common.base.Function; 073 074/** 075 * Resolves macros in the form of <code>%(key)</code> or <code>${key}</code> in an input String.<p> 076 * 077 * Starting with OpenCms 7.0, the preferred form of a macro is <code>%(key)</code>. This is to 078 * avoid conflicts / confusion with the JSP EL, which also uses the <code>${key}</code> syntax.<p> 079 * 080 * The macro names that can be resolved depend of the context objects provided to the resolver 081 * using the <code>set...</code> methods.<p> 082 * 083 * @since 6.0.0 084 */ 085public class CmsMacroResolver implements I_CmsMacroResolver { 086 087 /** The prefix indicating that the key represents an OpenCms runtime attribute. */ 088 public static final String KEY_ATTRIBUTE = "attribute."; 089 090 /** Key used to specify the context path as macro value. */ 091 public static final String KEY_CONTEXT_PATH = "contextPath"; 092 093 /** Key used to specify the description of the current organizational unit as macro value. */ 094 public static final String KEY_CURRENT_ORGUNIT_DESCRIPTION = "currentou.description"; 095 096 /** Key used to specify the full qualified name of the current organizational unit as macro value. */ 097 public static final String KEY_CURRENT_ORGUNIT_FQN = "currentou.fqn"; 098 099 /** Key used to specify the current time as macro value. */ 100 public static final String KEY_CURRENT_TIME = "currenttime"; 101 102 /** Key used to specify the city of the current user as macro value. */ 103 public static final String KEY_CURRENT_USER_CITY = "currentuser.city"; 104 105 /** Key used to specify the country of the current user as macro value. */ 106 public static final String KEY_CURRENT_USER_COUNTRY = "currentuser.country"; 107 108 /** Key used to specify the display name of the current user as macro value. */ 109 public static final String KEY_CURRENT_USER_DISPLAYNAME = "currentuser.displayname"; 110 111 /** Key used to specify the email address of the current user as macro value. */ 112 public static final String KEY_CURRENT_USER_EMAIL = "currentuser.email"; 113 114 /** Key used to specify the first name of the current user as macro value. */ 115 public static final String KEY_CURRENT_USER_FIRSTNAME = "currentuser.firstname"; 116 117 /** Key used to specify the full name of the current user as macro value. */ 118 public static final String KEY_CURRENT_USER_FULLNAME = "currentuser.fullname"; 119 120 /** Key used to specify the institution of the current user as macro value. */ 121 public static final String KEY_CURRENT_USER_INSTITUTION = "currentuser.institution"; 122 123 /** Key used to specify the last login date of the current user as macro value. */ 124 public static final String KEY_CURRENT_USER_LASTLOGIN = "currentuser.lastlogin"; 125 126 /** Key used to specify the last name of the current user as macro value. */ 127 public static final String KEY_CURRENT_USER_LASTNAME = "currentuser.lastname"; 128 129 /** Key used to specify the user name of the current user as macro value. */ 130 public static final String KEY_CURRENT_USER_NAME = "currentuser.name"; 131 132 /** Key used to specify the street of the current user as macro value. */ 133 public static final String KEY_CURRENT_USER_STREET = "currentuser.street"; 134 135 /** Key used to specify the zip code of the current user as macro value. */ 136 public static final String KEY_CURRENT_USER_ZIP = "currentuser.zip"; 137 138 /** Key prefix used to specify the value of a localized key as macro value. */ 139 public static final String KEY_LOCALIZED_PREFIX = "key."; 140 141 /** Identifier for "magic" parameter names. */ 142 public static final String KEY_OPENCMS = "opencms."; 143 144 /** The prefix indicating that the key represents a page context object. */ 145 public static final String KEY_PAGE_CONTEXT = "pageContext."; 146 147 /** Key used to specify the project id as macro value. */ 148 public static final String KEY_PROJECT_ID = "projectid"; 149 150 /** The prefix indicating that the key represents a property to be read on the current request URI. */ 151 public static final String KEY_PROPERTY = "property."; 152 153 /** The prefix indicating that the key represents a property to be read on the current element. */ 154 public static final String KEY_PROPERTY_ELEMENT = "elementProperty."; 155 156 /** Key used to specify a random id as macro value. */ 157 public static final String KEY_RANDOM_ID = "randomId"; 158 159 /** Key used to specify the request encoding as macro value. */ 160 public static final String KEY_REQUEST_ENCODING = "request.encoding"; 161 162 /** Key used to specify the folder of the request URI as macro value. */ 163 public static final String KEY_REQUEST_FOLDER = "request.folder"; 164 165 /** Key user to specify the request locale as macro value. */ 166 public static final String KEY_REQUEST_LOCALE = "request.locale"; 167 168 /** The prefix indicating that the key represents a HTTP request parameter. */ 169 public static final String KEY_REQUEST_PARAM = "param."; 170 171 /** Key used to specify the request site root as macro value. */ 172 public static final String KEY_REQUEST_SITEROOT = "request.siteroot"; 173 174 /** Key used to specify the request uri as macro value. */ 175 public static final String KEY_REQUEST_URI = "request.uri"; 176 177 /** Key used to specify the validation path as macro value. */ 178 public static final String KEY_VALIDATION_PATH = "validation.path"; 179 180 /** Key used to specify the validation regex as macro value. */ 181 public static final String KEY_VALIDATION_REGEX = "validation.regex"; 182 183 /** Key used to specify the validation value as macro value. */ 184 public static final String KEY_VALIDATION_VALUE = "validation.value"; 185 186 /** Identified for "magic" parameter commands. */ 187 static final String[] VALUE_NAMES_ARRAY = { 188 "uri", // 0 189 "filename", // 1 190 "folder", // 2 191 "default.encoding", // 3 192 "remoteaddress", // 4 193 "webapp", // 5 194 "webbasepath", // 6 195 "version", // 7 196 "versionid" // 8 197 }; 198 199 /** The "magic" commands wrapped in a List. */ 200 public static final List<String> VALUE_NAMES = Collections.unmodifiableList(Arrays.asList(VALUE_NAMES_ARRAY)); 201 202 /** The log object for this class. */ 203 private static final Log LOG = CmsLog.getLog(CmsMacroResolver.class); 204 205 /** A map of additional values provided by the calling class. */ 206 protected Map<String, String> m_additionalMacros; 207 208 /** The OpenCms user context to use for resolving macros. */ 209 protected CmsObject m_cms; 210 211 /** The JSP's page context to use for resolving macros. */ 212 protected PageContext m_jspPageContext; 213 214 /** Indicates if unresolved macros should be kept "as is" or replaced by an empty String. */ 215 protected boolean m_keepEmptyMacros; 216 217 /** The messages resource bundle to resolve localized keys with. */ 218 protected CmsMessages m_messages; 219 220 /** The request parameter map, used for better compatibility with multi part requests. */ 221 protected Map<String, String[]> m_parameterMap; 222 223 /** The resource name to use for resolving macros. */ 224 protected String m_resourceName; 225 226 /** A map from names of dynamic macros to the factories which generate their values. */ 227 private Map<String, Factory> m_factories; 228 229 /** 230 * Copies resources, adjust internal links (if adjustLinks==true) and resolves macros (if keyValue map is set).<p> 231 * 232 * @param cms CmsObject 233 * @param source path 234 * @param destination path 235 * @param keyValue map to be used for macro resolver 236 * @param adjustLinks boolean, true means internal links get adjusted. 237 * @throws CmsException exception 238 */ 239 public static void copyAndResolveMacro( 240 CmsObject cms, 241 String source, 242 String destination, 243 Map<String, String> keyValue, 244 boolean adjustLinks) 245 throws CmsException { 246 247 copyAndResolveMacro(cms, source, destination, keyValue, adjustLinks, CmsResource.COPY_AS_NEW); 248 } 249 250 /** 251 * Copies resources, adjust internal links (if adjustLinks==true) and resolves macros (if keyValue map is set).<p> 252 * 253 * @param cms CmsObject 254 * @param source path 255 * @param destination path 256 * @param keyValue map to be used for macro resolver 257 * @param adjustLinks boolean, true means internal links get adjusted. 258 * @param copyMode copyMode 259 * @throws CmsException exception 260 */ 261 262 public static void copyAndResolveMacro( 263 CmsObject cms, 264 String source, 265 String destination, 266 Map<String, String> keyValue, 267 boolean adjustLinks, 268 CmsResource.CmsResourceCopyMode copyMode) 269 throws CmsException { 270 271 copyAndResolveMacro(cms, source, destination, keyValue, adjustLinks, copyMode, null); 272 273 } 274 275 /** 276 * Copies resources, adjust internal links (if adjustLinks==true) and resolves macros (if keyValue map is set).<p> 277 * 278 * @param cms CmsObject 279 * @param source path 280 * @param destination path 281 * @param keyValue map to be used for macro resolver 282 * @param adjustLinks boolean, true means internal links get adjusted. 283 * @param copyMode copy Mode 284 * @param report report to write logs to 285 * @throws CmsException exception 286 */ 287 public static void copyAndResolveMacro( 288 CmsObject cms, 289 String source, 290 String destination, 291 Map<String, String> keyValue, 292 boolean adjustLinks, 293 CmsResource.CmsResourceCopyMode copyMode, 294 I_CmsReport report) 295 throws CmsException { 296 297 if (report != null) { 298 report.println( 299 org.opencms.ui.apps.Messages.get().container( 300 org.opencms.ui.apps.Messages.RPT_MACRORESOLVER_COPY_RESOURCES_1, 301 source)); 302 } 303 cms.copyResource(source, destination, copyMode); 304 if (report != null) { 305 report.println( 306 org.opencms.ui.apps.Messages.get().container( 307 org.opencms.ui.apps.Messages.RPT_MACRORESOLVER_LINK_ADJUST_0)); 308 } 309 if (adjustLinks) { 310 cms.adjustLinks(source, destination); 311 } 312 //Guards to check if keyValue is set correctly, otherwise no adjustment is done 313 if (keyValue == null) { 314 return; 315 } 316 if (keyValue.isEmpty()) { 317 return; 318 } 319 320 if (report != null) { 321 report.println( 322 org.opencms.ui.apps.Messages.get().container( 323 org.opencms.ui.apps.Messages.RPT_MACRORESOLVER_APPLY_MACROS_0)); 324 } 325 326 CmsMacroResolver macroResolver = new CmsMacroResolver(); 327 macroResolver.setKeepEmptyMacros(true); 328 for (String key : keyValue.keySet()) { 329 330 macroResolver.addMacro(key, keyValue.get(key)); 331 } 332 333 //Collect all resources to loop over 334 List<CmsResource> resoucesToCopy = cms.readResources(destination, CmsResourceFilter.ALL, true); 335 for (CmsResource resource : resoucesToCopy) { 336 if (resource.isFile() 337 && (resource.getTypeId() != CmsResourceTypeBinary.getStaticTypeId()) 338 && (resource.getTypeId() != CmsResourceTypeImage.getStaticTypeId())) { 339 CmsFile file = cms.readFile(resource); 340 CmsMacroResolver.updateFile(cms, file, macroResolver); 341 } 342 CmsMacroResolver.updateProperties(cms, resource, macroResolver); 343 } 344 345 // apply macro to the folder itself 346 CmsResource resource = cms.readResource(destination, CmsResourceFilter.ALL); 347 348 CmsMacroResolver.updateProperties(cms, resource, macroResolver); 349 350 if (cms.existsResource(ensureFoldername(destination) + CmsSiteManager.MACRO_FOLDER)) { 351 cms.deleteResource( 352 ensureFoldername(destination) + CmsSiteManager.MACRO_FOLDER, 353 CmsResource.CmsResourceDeleteMode.valueOf(-1)); 354 } 355 356 } 357 358 /** 359 * Adds macro delimiters to the given input, 360 * for example <code>key</code> becomes <code>%(key)</code>.<p> 361 * 362 * @param input the input to format as a macro 363 * 364 * @return the input formatted as a macro 365 */ 366 public static String formatMacro(String input) { 367 368 StringBuffer result = new StringBuffer(input.length() + 4); 369 result.append(I_CmsMacroResolver.MACRO_DELIMITER); 370 result.append(I_CmsMacroResolver.MACRO_START); 371 result.append(input); 372 result.append(I_CmsMacroResolver.MACRO_END); 373 return result.toString(); 374 } 375 376 /** 377 * Reads a bundle (key, value, descriptor) from Descriptor Resource and property resource.<p> 378 * 379 * @param resourceBundle property resource 380 * @param descriptor resource 381 * @param clonedCms cms instance 382 * @return Map <key, [value, descriptor]> 383 * @throws CmsXmlException exception 384 * @throws CmsException exception 385 */ 386 public static Map<String, String[]> getBundleMapFromResources( 387 Properties resourceBundle, 388 CmsResource descriptor, 389 CmsObject clonedCms) 390 throws CmsXmlException, CmsException { 391 392 Map<String, String[]> ret = new LinkedHashMap<String, String[]>(); 393 394 //Read XML content of descriptor 395 CmsXmlContent xmlContentDesc = CmsXmlContentFactory.unmarshal(clonedCms, clonedCms.readFile(descriptor)); 396 CmsXmlContentValueSequence messages = xmlContentDesc.getValueSequence(Descriptor.N_MESSAGE, Descriptor.LOCALE); 397 398 //Iterate through content 399 for (int i = 0; i < messages.getElementCount(); i++) { 400 401 //Read key and default text from descriptor, label from bundle (localized) 402 String prefix = messages.getValue(i).getPath() + "/"; 403 String key = xmlContentDesc.getValue(prefix + Descriptor.N_KEY, Descriptor.LOCALE).getStringValue( 404 clonedCms); 405 String label = resourceBundle.getProperty(key); 406 String defaultText = xmlContentDesc.getValue( 407 prefix + Descriptor.N_DESCRIPTION, 408 Descriptor.LOCALE).getStringValue(clonedCms); 409 410 ret.put(key, new String[] {label, defaultText}); 411 } 412 return ret; 413 } 414 415 /** 416 * Returns <code>true</code> if the given input String if formatted like a macro, 417 * that is it starts with <code>{@link I_CmsMacroResolver#MACRO_DELIMITER_OLD} + 418 * {@link I_CmsMacroResolver#MACRO_START_OLD}</code> and ends with 419 * <code>{@link I_CmsMacroResolver#MACRO_END_OLD}</code>.<p> 420 * 421 * @param input the input to check for a macro 422 * @return <code>true</code> if the given input String if formatted like a macro 423 */ 424 public static boolean isMacro(String input) { 425 426 if (CmsStringUtil.isEmpty(input) || (input.length() < 3)) { 427 return false; 428 } 429 430 return (((input.charAt(0) == I_CmsMacroResolver.MACRO_DELIMITER_OLD) 431 && ((input.charAt(1) == I_CmsMacroResolver.MACRO_START_OLD) 432 && (input.charAt(input.length() - 1) == I_CmsMacroResolver.MACRO_END_OLD))) 433 || ((input.charAt(0) == I_CmsMacroResolver.MACRO_DELIMITER) 434 && ((input.charAt(1) == I_CmsMacroResolver.MACRO_START) 435 && (input.charAt(input.length() - 1) == I_CmsMacroResolver.MACRO_END)))); 436 } 437 438 /** 439 * Returns <code>true</code> if the given input String is a macro equal to the given macro name.<p> 440 * 441 * @param input the input to check for a macro 442 * @param macroName the macro name to check for 443 * 444 * @return <code>true</code> if the given input String is a macro equal to the given macro name 445 */ 446 public static boolean isMacro(String input, String macroName) { 447 448 if (isMacro(input)) { 449 return input.substring(2, input.length() - 1).equals(macroName); 450 } 451 return false; 452 } 453 454 /** 455 * Returns a macro for the given localization key with the given parameters.<p> 456 * 457 * @param keyName the name of the localized key 458 * @param params the optional parameter array 459 * 460 * @return a macro for the given localization key with the given parameters 461 */ 462 public static String localizedKeyMacro(String keyName, Object[] params) { 463 464 String parameters = ""; 465 if ((params != null) && (params.length > 0)) { 466 for (int i = 0; i < params.length; i++) { 467 if (params[i] != null) { 468 parameters += "|" + params[i].toString(); 469 } 470 } 471 } 472 return "" 473 + I_CmsMacroResolver.MACRO_DELIMITER 474 + I_CmsMacroResolver.MACRO_START 475 + CmsMacroResolver.KEY_LOCALIZED_PREFIX 476 + keyName 477 + parameters 478 + I_CmsMacroResolver.MACRO_END; 479 } 480 481 /** 482 * Factory method to create a new {@link CmsMacroResolver} instance.<p> 483 * 484 * @return a new instance of a {@link CmsMacroResolver} 485 */ 486 public static CmsMacroResolver newInstance() { 487 488 return new CmsMacroResolver(); 489 } 490 491 /** Returns a new macro resolver that loads message keys from the workplace bundle in the user setting's language. 492 * @param cms the CmsObject. 493 * @return a new macro resolver with messages from the workplace bundle in the current users locale. 494 */ 495 public static I_CmsMacroResolver newWorkplaceLocaleResolver(final CmsObject cms) { 496 497 // Resolve macros in the property configuration 498 CmsMacroResolver resolver = new CmsMacroResolver(); 499 resolver.setCmsObject(cms); 500 CmsUserSettings userSettings = new CmsUserSettings(cms.getRequestContext().getCurrentUser()); 501 CmsMultiMessages multimessages = new CmsMultiMessages(userSettings.getLocale()); 502 multimessages.addMessages(OpenCms.getWorkplaceManager().getMessages(userSettings.getLocale())); 503 resolver.setMessages(multimessages); 504 resolver.setKeepEmptyMacros(true); 505 506 return resolver; 507 } 508 509 /** 510 * Resolves the macros in the given input using the provided parameters.<p> 511 * 512 * A macro in the form <code>%(key)</code> or <code>${key}</code> in the content is replaced with it's assigned value 513 * returned by the <code>{@link I_CmsMacroResolver#getMacroValue(String)}</code> method of the given 514 * <code>{@link I_CmsMacroResolver}</code> instance.<p> 515 * 516 * If a macro is found that can not be mapped to a value by the given macro resolver, 517 * it is left untouched in the input.<p> 518 * 519 * @param input the input in which to resolve the macros 520 * @param cms the OpenCms user context to use when resolving macros 521 * @param messages the message resource bundle to use when resolving macros 522 * 523 * @return the input with the macros resolved 524 */ 525 public static String resolveMacros(String input, CmsObject cms, CmsMessages messages) { 526 527 CmsMacroResolver resolver = new CmsMacroResolver(); 528 resolver.m_cms = cms; 529 resolver.m_messages = messages; 530 resolver.m_keepEmptyMacros = true; 531 return resolver.resolveMacros(input); 532 } 533 534 /** 535 * Resolves macros in the provided input String using the given macro resolver.<p> 536 * 537 * A macro in the form <code>%(key)</code> or <code>${key}</code> in the content is replaced with it's assigned value 538 * returned by the <code>{@link I_CmsMacroResolver#getMacroValue(String)}</code> method of the given 539 * <code>{@link I_CmsMacroResolver}</code> instance.<p> 540 * 541 * If a macro is found that can not be mapped to a value by the given macro resolver, 542 * <code>{@link I_CmsMacroResolver#isKeepEmptyMacros()}</code> controls if the macro is replaced by 543 * an empty String, or is left untouched in the input.<p> 544 * 545 * @param input the input in which to resolve the macros 546 * @param resolver the macro resolver to use 547 * 548 * @return the input with all macros resolved 549 */ 550 public static String resolveMacros(final String input, I_CmsMacroResolver resolver) { 551 552 if ((input == null) || (input.length() < 3)) { 553 // macro must have at last 3 chars "${}" or "%()" 554 return input; 555 } 556 557 int pn = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER); 558 int po = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER_OLD); 559 560 if ((po == -1) && (pn == -1)) { 561 // no macro delimiter found in input 562 return input; 563 } 564 565 int len = input.length(); 566 StringBuffer result = new StringBuffer(len << 1); 567 int np, pp1, pp2, e; 568 String macro, value; 569 boolean keep = resolver.isKeepEmptyMacros(); 570 boolean resolvedNone = true; 571 char ds, de; 572 int p; 573 574 if ((po == -1) || ((pn > -1) && (pn < po))) { 575 p = pn; 576 ds = I_CmsMacroResolver.MACRO_START; 577 de = I_CmsMacroResolver.MACRO_END; 578 } else { 579 p = po; 580 ds = I_CmsMacroResolver.MACRO_START_OLD; 581 de = I_CmsMacroResolver.MACRO_END_OLD; 582 } 583 584 // append chars before the first delimiter found 585 result.append(input.substring(0, p)); 586 do { 587 pp1 = p + 1; 588 pp2 = pp1 + 1; 589 if (pp2 >= len) { 590 // remaining chars can't be a macro (minimum size is 3) 591 result.append(input.substring(p, len)); 592 break; 593 } 594 // get the next macro delimiter 595 if ((pn > -1) && (pn < pp1)) { 596 pn = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER, pp1); 597 } 598 if ((po > -1) && (po < pp1)) { 599 po = input.indexOf(I_CmsMacroResolver.MACRO_DELIMITER_OLD, pp1); 600 } 601 if ((po == -1) && (pn == -1)) { 602 // none found, make sure remaining chars in this segment are appended 603 np = len; 604 } else { 605 // check if the next delimiter is old or new style 606 if ((po == -1) || ((pn > -1) && (pn < po))) { 607 np = pn; 608 } else { 609 np = po; 610 } 611 } 612 // check if the next char is a "macro start" 613 char st = input.charAt(pp1); 614 if (st == ds) { 615 // we have a starting macro sequence "${" or "%(", now check if this segment contains a "}" or ")" 616 e = input.indexOf(de, p); 617 if ((e > 0) && (e < np)) { 618 // this segment contains a closing macro delimiter "}" or "]", so we may have found a macro 619 macro = input.substring(pp2, e); 620 // resolve macro 621 value = resolver.getMacroValue(macro); 622 e++; 623 if (value != null) { 624 // macro was successfully resolved 625 result.append(value); 626 resolvedNone = false; 627 } else if (keep) { 628 // macro was unknown, but should be kept 629 result.append(input.substring(p, e)); 630 } 631 } else { 632 // no complete macro "${...}" or "%(...)" in this segment 633 e = p; 634 } 635 } else { 636 // no macro start char after the "$" or "%" 637 e = p; 638 } 639 // set macro style for next delimiter found 640 if (np == pn) { 641 ds = I_CmsMacroResolver.MACRO_START; 642 de = I_CmsMacroResolver.MACRO_END; 643 } else { 644 ds = I_CmsMacroResolver.MACRO_START_OLD; 645 de = I_CmsMacroResolver.MACRO_END_OLD; 646 } 647 // append the remaining chars after the macro to the start of the next macro 648 result.append(input.substring(e, np)); 649 // this is a nerdy joke ;-) 650 p = np; 651 } while (p < len); 652 653 if (resolvedNone && keep) { 654 // nothing was resolved and macros should be kept, return original input 655 return input; 656 } 657 658 // input was changed during resolving of macros 659 return result.toString(); 660 } 661 662 /** 663 * Strips the macro delimiters from the given input, 664 * for example <code>%(key)</code> or <code>${key}</code> becomes <code>key</code>.<p> 665 * 666 * In case the input is not a macro, <code>null</code> is returned.<p> 667 * 668 * @param input the input to strip 669 * 670 * @return the macro stripped from the input, or <code>null</code> 671 */ 672 public static String stripMacro(String input) { 673 674 if (isMacro(input)) { 675 return input.substring(2, input.length() - 1); 676 } 677 return null; 678 } 679 680 /** 681 * Checks if there are at least one character in the folder name, 682 * also ensures that it ends with a '/' and doesn't start with '/'.<p> 683 * 684 * @param resourcename folder name to check (complete path) 685 * @return the validated folder name 686 * @throws CmsIllegalArgumentException if the folder name is empty or <code>null</code> 687 */ 688 private static String ensureFoldername(String resourcename) throws CmsIllegalArgumentException { 689 690 if (CmsStringUtil.isEmpty(resourcename)) { 691 throw new CmsIllegalArgumentException( 692 org.opencms.db.Messages.get().container(org.opencms.db.Messages.ERR_BAD_RESOURCENAME_1, resourcename)); 693 } 694 if (!CmsResource.isFolder(resourcename)) { 695 resourcename = resourcename.concat("/"); 696 } 697 if (resourcename.charAt(0) == '/') { 698 resourcename = resourcename.substring(1); 699 } 700 return resourcename; 701 } 702 703 /** 704 * Updates a single file with the given macro resolver.<p> 705 * 706 * @param cms the cms context 707 * @param file the file to update 708 * @param macroResolver the macro resolver to update with 709 * 710 * @throws CmsException if something goes wrong 711 */ 712 private static void updateFile(CmsObject cms, CmsFile file, CmsMacroResolver macroResolver) throws CmsException { 713 714 String encoding = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue( 715 OpenCms.getSystemInfo().getDefaultEncoding()); 716 String content; 717 try { 718 content = macroResolver.resolveMacros(new String(file.getContents(), encoding)); 719 } catch (UnsupportedEncodingException e) { 720 try { 721 content = macroResolver.resolveMacros( 722 new String(file.getContents(), Charset.defaultCharset().toString())); 723 } catch (UnsupportedEncodingException e1) { 724 content = macroResolver.resolveMacros(new String(file.getContents())); 725 } 726 } 727 // update the content 728 try { 729 file.setContents(content.getBytes(encoding)); 730 } catch (UnsupportedEncodingException e) { 731 try { 732 file.setContents(content.getBytes(Charset.defaultCharset().toString())); 733 } catch (UnsupportedEncodingException e1) { 734 file.setContents(content.getBytes()); 735 } 736 } 737 // write the target file 738 cms.writeFile(file); 739 } 740 741 /** 742 * Updates all properties of the given resource with the given macro resolver.<p> 743 * 744 * @param cms the cms context 745 * @param resource the resource to update the properties for 746 * @param macroResolver the macro resolver to use 747 * 748 * @throws CmsException if something goes wrong 749 */ 750 private static void updateProperties(CmsObject cms, CmsResource resource, CmsMacroResolver macroResolver) 751 throws CmsException { 752 753 Iterator<CmsProperty> it = cms.readPropertyObjects(resource, false).iterator(); 754 while (it.hasNext()) { 755 CmsProperty property = it.next(); 756 String resValue = null; 757 if (property.getResourceValue() != null) { 758 resValue = macroResolver.resolveMacros(property.getResourceValue()); 759 } 760 String strValue = null; 761 if (property.getStructureValue() != null) { 762 strValue = macroResolver.resolveMacros(property.getStructureValue()); 763 } 764 CmsProperty newProperty = new CmsProperty(property.getName(), strValue, resValue); 765 cms.writePropertyObject(cms.getSitePath(resource), newProperty); 766 } 767 } 768 769 /** 770 * Adds a macro whose value will be dynamically generated at macro resolution time.<p> 771 * 772 * The value will be generated for each occurence of the macro in a string.<p> 773 * 774 * @param name the name of the macro 775 * @param factory the macro value generator 776 */ 777 public void addDynamicMacro(String name, Factory factory) { 778 779 if (m_factories == null) { 780 m_factories = new HashMap<String, Factory>(); 781 } 782 m_factories.put(name, factory); 783 } 784 785 /** 786 * Adds a customized macro to this macro resolver.<p> 787 * 788 * @param key the macro to add 789 * @param value the value to return if the macro is encountered 790 */ 791 public void addMacro(String key, String value) { 792 793 if (m_additionalMacros == null) { 794 // use lazy initializing 795 m_additionalMacros = new HashMap<String, String>(); 796 } 797 m_additionalMacros.put(key, value); 798 } 799 800 /** 801 * @see org.opencms.util.I_CmsMacroResolver#getMacroValue(java.lang.String) 802 */ 803 public String getMacroValue(String macro) { 804 805 if (m_messages != null) { 806 if (macro.startsWith(CmsMacroResolver.KEY_LOCALIZED_PREFIX)) { 807 String keyName = macro.substring(CmsMacroResolver.KEY_LOCALIZED_PREFIX.length()); 808 return m_messages.keyWithParams(keyName); 809 } 810 } 811 812 if (m_factories != null) { 813 Factory factory = m_factories.get(macro); 814 if (factory != null) { 815 String value = (String)factory.create(); 816 return value; 817 } 818 } 819 820 if (m_jspPageContext != null) { 821 822 if (m_jspPageContext.getRequest() != null) { 823 if (macro.startsWith(CmsMacroResolver.KEY_REQUEST_PARAM)) { 824 // the key is a request parameter 825 macro = macro.substring(CmsMacroResolver.KEY_REQUEST_PARAM.length()); 826 String result = null; 827 if (m_parameterMap != null) { 828 String[] param = m_parameterMap.get(macro); 829 if ((param != null) && (param.length >= 1)) { 830 result = param[0]; 831 } 832 } else { 833 result = m_jspPageContext.getRequest().getParameter(macro); 834 } 835 if ((result == null) && macro.equals(KEY_PROJECT_ID)) { 836 result = m_cms.getRequestContext().getCurrentProject().getUuid().toString(); 837 } 838 return result; 839 } 840 841 if ((m_cms != null) && macro.startsWith(CmsMacroResolver.KEY_PROPERTY_ELEMENT)) { 842 843 // the key is a cms property to be read on the current element 844 845 macro = macro.substring(CmsMacroResolver.KEY_PROPERTY_ELEMENT.length()); 846 CmsFlexController controller = CmsFlexController.getController(m_jspPageContext.getRequest()); 847 try { 848 CmsProperty property = m_cms.readPropertyObject( 849 controller.getCurrentRequest().getElementUri(), 850 macro, 851 false); 852 if (property != CmsProperty.getNullProperty()) { 853 return property.getValue(); 854 } 855 } catch (CmsException e) { 856 if (LOG.isWarnEnabled()) { 857 LOG.warn( 858 Messages.get().getBundle().key( 859 Messages.LOG_PROPERTY_READING_FAILED_2, 860 macro, 861 controller.getCurrentRequest().getElementUri()), 862 e); 863 } 864 } 865 } 866 } 867 868 if (macro.startsWith(CmsMacroResolver.KEY_PAGE_CONTEXT)) { 869 // the key is a page context object 870 macro = macro.substring(CmsMacroResolver.KEY_PAGE_CONTEXT.length()); 871 int scope = m_jspPageContext.getAttributesScope(macro); 872 return m_jspPageContext.getAttribute(macro, scope).toString(); 873 } 874 } 875 876 if (m_cms != null) { 877 878 if (macro.startsWith(CmsMacroResolver.KEY_PROPERTY)) { 879 // the key is a cms property to be read on the current request URI 880 macro = macro.substring(CmsMacroResolver.KEY_PROPERTY.length()); 881 try { 882 CmsProperty property = m_cms.readPropertyObject(m_cms.getRequestContext().getUri(), macro, true); 883 if (property != CmsProperty.getNullProperty()) { 884 return property.getValue(); 885 } 886 } catch (CmsException e) { 887 if (LOG.isWarnEnabled()) { 888 CmsMessageContainer message = Messages.get().container( 889 Messages.LOG_PROPERTY_READING_FAILED_2, 890 macro, 891 m_cms.getRequestContext().getUri()); 892 LOG.warn(message.key(), e); 893 } 894 } 895 return null; 896 } 897 898 if (macro.startsWith(CmsMacroResolver.KEY_ATTRIBUTE)) { 899 // the key is an OpenCms runtime attribute 900 macro = macro.substring(CmsMacroResolver.KEY_ATTRIBUTE.length()); 901 Object attribute = m_cms.getRequestContext().getAttribute(macro); 902 if (attribute != null) { 903 return attribute.toString(); 904 } 905 return null; 906 } 907 908 if (macro.startsWith(CmsMacroResolver.KEY_OPENCMS)) { 909 910 // the key is a shortcut for a cms runtime value 911 912 String originalKey = macro; 913 macro = macro.substring(CmsMacroResolver.KEY_OPENCMS.length()); 914 int index = VALUE_NAMES.indexOf(macro); 915 String value = null; 916 917 switch (index) { 918 case 0: 919 // "uri" 920 value = m_cms.getRequestContext().getUri(); 921 break; 922 case 1: 923 // "filename" 924 value = m_resourceName; 925 break; 926 case 2: 927 // folder 928 value = m_cms.getRequestContext().getFolderUri(); 929 break; 930 case 3: 931 // default.encoding 932 value = OpenCms.getSystemInfo().getDefaultEncoding(); 933 break; 934 case 4: 935 // remoteaddress 936 value = m_cms.getRequestContext().getRemoteAddress(); 937 break; 938 case 5: 939 // webapp 940 value = OpenCms.getSystemInfo().getWebApplicationName(); 941 break; 942 case 6: 943 // webbasepath 944 value = OpenCms.getSystemInfo().getWebApplicationRfsPath(); 945 break; 946 case 7: 947 // version 948 value = OpenCms.getSystemInfo().getVersionNumber(); 949 break; 950 case 8: 951 // versionid 952 value = OpenCms.getSystemInfo().getVersionId(); 953 break; 954 default: 955 // return the key "as is" 956 value = originalKey; 957 break; 958 } 959 960 return value; 961 } 962 963 if (CmsMacroResolver.KEY_CURRENT_USER_NAME.equals(macro)) { 964 // the key is the current users login name 965 return m_cms.getRequestContext().getCurrentUser().getName(); 966 } 967 968 if (CmsMacroResolver.KEY_CURRENT_USER_FIRSTNAME.equals(macro)) { 969 // the key is the current users first name 970 return m_cms.getRequestContext().getCurrentUser().getFirstname(); 971 } 972 973 if (CmsMacroResolver.KEY_CURRENT_USER_LASTNAME.equals(macro)) { 974 // the key is the current users last name 975 return m_cms.getRequestContext().getCurrentUser().getLastname(); 976 } 977 978 if (CmsMacroResolver.KEY_CURRENT_USER_DISPLAYNAME.equals(macro)) { 979 // the key is the current users display name 980 try { 981 if (m_messages != null) { 982 return m_cms.getRequestContext().getCurrentUser().getDisplayName(m_cms, m_messages.getLocale()); 983 } else { 984 return m_cms.getRequestContext().getCurrentUser().getDisplayName( 985 m_cms, 986 m_cms.getRequestContext().getLocale()); 987 } 988 } catch (CmsException e) { 989 // ignore, macro can not be resolved 990 } 991 } 992 993 if (CmsMacroResolver.KEY_CURRENT_ORGUNIT_FQN.equals(macro)) { 994 // the key is the current organizational unit fully qualified name 995 return m_cms.getRequestContext().getOuFqn(); 996 } 997 998 if (CmsMacroResolver.KEY_CURRENT_ORGUNIT_DESCRIPTION.equals(macro)) { 999 // the key is the current organizational unit description 1000 try { 1001 CmsOrganizationalUnit ou = OpenCms.getOrgUnitManager().readOrganizationalUnit( 1002 m_cms, 1003 m_cms.getRequestContext().getOuFqn()); 1004 if (m_messages != null) { 1005 return ou.getDescription(m_messages.getLocale()); 1006 } else { 1007 return ou.getDescription(m_cms.getRequestContext().getLocale()); 1008 } 1009 } catch (CmsException e) { 1010 // ignore, macro can not be resolved 1011 } 1012 } 1013 1014 if (CmsMacroResolver.KEY_CURRENT_USER_FULLNAME.equals(macro)) { 1015 // the key is the current users full name 1016 return m_cms.getRequestContext().getCurrentUser().getFullName(); 1017 } 1018 1019 if (CmsMacroResolver.KEY_CURRENT_USER_EMAIL.equals(macro)) { 1020 // the key is the current users email address 1021 return m_cms.getRequestContext().getCurrentUser().getEmail(); 1022 } 1023 1024 if (CmsMacroResolver.KEY_CURRENT_USER_STREET.equals(macro)) { 1025 // the key is the current users address 1026 return m_cms.getRequestContext().getCurrentUser().getAddress(); 1027 } 1028 1029 if (CmsMacroResolver.KEY_CURRENT_USER_ZIP.equals(macro)) { 1030 // the key is the current users zip code 1031 return m_cms.getRequestContext().getCurrentUser().getZipcode(); 1032 } 1033 1034 if (CmsMacroResolver.KEY_CURRENT_USER_COUNTRY.equals(macro)) { 1035 // the key is the current users country 1036 return m_cms.getRequestContext().getCurrentUser().getCountry(); 1037 } 1038 1039 if (CmsMacroResolver.KEY_CURRENT_USER_CITY.equals(macro)) { 1040 // the key is the current users city 1041 return m_cms.getRequestContext().getCurrentUser().getCity(); 1042 } 1043 1044 if (CmsMacroResolver.KEY_CURRENT_USER_LASTLOGIN.equals(macro) && (m_messages != null)) { 1045 // the key is the current users last login timestamp 1046 return m_messages.getDateTime(m_cms.getRequestContext().getCurrentUser().getLastlogin()); 1047 } 1048 1049 if (CmsMacroResolver.KEY_REQUEST_SITEROOT.equals(macro)) { 1050 // the key is the currently requested site root 1051 return m_cms.getRequestContext().getSiteRoot(); 1052 } 1053 1054 if (CmsMacroResolver.KEY_REQUEST_URI.equals(macro)) { 1055 // the key is the currently requested uri 1056 return m_cms.getRequestContext().getUri(); 1057 } 1058 1059 if (CmsMacroResolver.KEY_REQUEST_FOLDER.equals(macro)) { 1060 // the key is the currently requested folder 1061 return CmsResource.getParentFolder(m_cms.getRequestContext().getUri()); 1062 } 1063 1064 if (CmsMacroResolver.KEY_REQUEST_ENCODING.equals(macro)) { 1065 // the key is the current encoding of the request 1066 return m_cms.getRequestContext().getEncoding(); 1067 } 1068 1069 if (CmsMacroResolver.KEY_REQUEST_LOCALE.equals(macro)) { 1070 // the key is the current locale of the request 1071 return m_cms.getRequestContext().getLocale().toString(); 1072 } 1073 1074 if (CmsMacroResolver.KEY_CONTEXT_PATH.equals(macro)) { 1075 // the key is the OpenCms context path 1076 return OpenCms.getSystemInfo().getContextPath(); 1077 } 1078 1079 if (CmsMacroResolver.KEY_CURRENT_USER_INSTITUTION.equals(macro)) { 1080 // the key is the current users institution 1081 return m_cms.getRequestContext().getCurrentUser().getInstitution(); 1082 } 1083 1084 } 1085 1086 if (CmsMacroResolver.KEY_CURRENT_TIME.equals(macro)) { 1087 // the key is the current system time 1088 return String.valueOf(System.currentTimeMillis()); 1089 } else if (macro.startsWith(CmsMacroResolver.KEY_CURRENT_TIME)) { 1090 // the key starts with the current system time 1091 macro = macro.substring(CmsMacroResolver.KEY_CURRENT_TIME.length()).trim(); 1092 char operator = macro.charAt(0); 1093 macro = macro.substring(1).trim(); 1094 long delta = 0; 1095 try { 1096 delta = Long.parseLong(macro); 1097 } catch (NumberFormatException e) { 1098 // ignore, there will be no delta 1099 } 1100 long resultTime = System.currentTimeMillis(); 1101 switch (operator) { 1102 case '+': 1103 // add delta to current time 1104 resultTime += delta; 1105 break; 1106 case '-': 1107 // subtract delta from current time 1108 resultTime -= delta; 1109 break; 1110 default: 1111 break; 1112 } 1113 return String.valueOf(resultTime); 1114 } 1115 1116 if (CmsMacroResolver.KEY_RANDOM_ID.equals(macro)) { 1117 // a random id value is requested 1118 String id = CmsUUID.getConstantUUID("randomId." + Math.random()).toString(); 1119 // full UUIDs are to long, the first part should be enough 1120 return id.substring(0, id.indexOf('-')); 1121 } 1122 1123 if (m_additionalMacros != null) { 1124 return m_additionalMacros.get(macro); 1125 } 1126 1127 return null; 1128 } 1129 1130 /** 1131 * @see org.opencms.util.I_CmsMacroResolver#isKeepEmptyMacros() 1132 */ 1133 public boolean isKeepEmptyMacros() { 1134 1135 return m_keepEmptyMacros; 1136 } 1137 1138 /** 1139 * Resolves the macros in the given input.<p> 1140 * 1141 * Calls <code>{@link #resolveMacros(String)}</code> until no more macros can 1142 * be resolved in the input. This way "nested" macros in the input are resolved as well.<p> 1143 * 1144 * @see org.opencms.util.I_CmsMacroResolver#resolveMacros(java.lang.String) 1145 */ 1146 public String resolveMacros(String input) { 1147 1148 String result = input; 1149 1150 if (input != null) { 1151 String lastResult; 1152 do { 1153 // save result for next comparison 1154 lastResult = result; 1155 // resolve the macros 1156 result = CmsMacroResolver.resolveMacros(result, this); 1157 // if nothing changes then the final result is found 1158 } while (!result.equals(lastResult)); 1159 } 1160 1161 // return the result 1162 return result; 1163 } 1164 1165 /** 1166 * Provides a set of additional macros to this macro resolver.<p> 1167 * 1168 * Macros added with {@link #addMacro(String, String)} are added to the same set 1169 * 1170 * @param additionalMacros the additional macros to add 1171 * 1172 * @return this instance of the macro resolver 1173 */ 1174 public CmsMacroResolver setAdditionalMacros(Map<String, String> additionalMacros) { 1175 1176 m_additionalMacros = additionalMacros; 1177 return this; 1178 } 1179 1180 /** 1181 * Provides an OpenCms user context to this macro resolver, required to resolve certain macros.<p> 1182 * 1183 * @param cms the OpenCms user context 1184 * 1185 * @return this instance of the macro resolver 1186 */ 1187 public CmsMacroResolver setCmsObject(CmsObject cms) { 1188 1189 m_cms = cms; 1190 return this; 1191 } 1192 1193 /** 1194 * Provides a JSP page context to this macro resolver, required to resolve certain macros.<p> 1195 * 1196 * @param jspPageContext the JSP page context to use 1197 * 1198 * @return this instance of the macro resolver 1199 */ 1200 public CmsMacroResolver setJspPageContext(PageContext jspPageContext) { 1201 1202 m_jspPageContext = jspPageContext; 1203 return this; 1204 } 1205 1206 /** 1207 * Controls of macros that can't be resolved are left unchanged in the input, 1208 * or are replaced with an empty String.<p> 1209 * 1210 * @param keepEmptyMacros the replacement flag to use 1211 * 1212 * @return this instance of the macro resolver 1213 * 1214 * @see #isKeepEmptyMacros() 1215 */ 1216 public CmsMacroResolver setKeepEmptyMacros(boolean keepEmptyMacros) { 1217 1218 m_keepEmptyMacros = keepEmptyMacros; 1219 return this; 1220 } 1221 1222 /** 1223 * Provides a set of <code>{@link CmsMessages}</code> to this macro resolver, 1224 * required to resolve localized macros.<p> 1225 * 1226 * @param messages the message resource bundle to use 1227 * 1228 * @return this instance of the macro resolver 1229 */ 1230 public CmsMacroResolver setMessages(CmsMessages messages) { 1231 1232 m_messages = messages; 1233 return this; 1234 } 1235 1236 /** 1237 * Sets the parameter map.<p> 1238 * 1239 * @param parameterMap the parameter map to set 1240 */ 1241 public void setParameterMap(Map<String, String[]> parameterMap) { 1242 1243 m_parameterMap = parameterMap; 1244 } 1245 1246 /** 1247 * Provides a resource name to this macro resolver, required to resolve certain macros.<p> 1248 * 1249 * @param resourceName the resource name to use 1250 * 1251 * @return this instance of the macro resolver 1252 */ 1253 public CmsMacroResolver setResourceName(String resourceName) { 1254 1255 m_resourceName = resourceName; 1256 return this; 1257 } 1258 1259 /** 1260 * Returns a function which applies the macro substitution of this resolver to its argument.<p> 1261 * 1262 * @return a function performing string substitution with this resolver 1263 */ 1264 public Function<String, String> toFunction() { 1265 1266 return new Function<String, String>() { 1267 1268 public String apply(String input) { 1269 1270 return resolveMacros(input); 1271 1272 } 1273 }; 1274 } 1275}