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