org.forgerock.openidm.util.ConfigMacroUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openidm.util.ConfigMacroUtil.java

Source

/**
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2015 ForgeRock AS. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://forgerock.org/license/CDDLv1.0.html
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at http://forgerock.org/license/CDDLv1.0.html
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 */
package org.forgerock.openidm.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.forgerock.json.JsonValue;
import org.forgerock.openidm.core.ServerConstants;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Hours;
import org.joda.time.Minutes;
import org.joda.time.Months;
import org.joda.time.ReadablePeriod;
import org.joda.time.Seconds;
import org.joda.time.Years;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class ConfigMacroUtil {

    /**
     * Setup logging for the {@link ConfigMacroUtil}.
     */
    private final static Logger logger = LoggerFactory.getLogger(ConfigMacroUtil.class);

    private final static DateUtil DATE_UTIL = DateUtil.getDateUtil(ServerConstants.TIME_ZONE_UTC);

    private ConfigMacroUtil() {
    }

    /**
     * Expands any interpolation contained within the JsonValue object in-place.
     *
     * @param json
     *            JsonValue to parse for macros
     */
    public static void expand(JsonValue json) {
        Iterator<String> iter = json.keys().iterator();
        while (iter.hasNext()) {
            String key = iter.next();

            String expanded = parse(json.get(key));
            if (expanded != null) {
                json.put(key, expanded);
            }
        }
    }

    /**
     * Start the string parsing. If base is not a string, will return null.
     *
     * @param base
     *            base JsonValue object to begin parsing from
     * @return a string with any interpolation expanded or null if base is not a
     *         string
     */
    private static String parse(JsonValue base) {
        if (!base.isString()) {
            return null;
        }

        return buildString(base.asString());
    }

    /**
     * Begins building the string from interpolation and normal string contents.
     *
     * @param str
     *            string to interpolate from
     * @return a string after interpolation
     */
    public static String buildString(String str) {
        StringBuilder builder = new StringBuilder();

        List<Integer> possibleLocations = possibleLocations(str);
        if (possibleLocations.isEmpty()) {
            return null;
        }

        List<Integer[]> confirmedLocations = confirmedLocations(str, possibleLocations);
        if (confirmedLocations.isEmpty()) {
            return null;
        }

        int lastEnd = 0;
        for (Integer[] pair : confirmedLocations) {
            int start = pair[0];
            int length = pair[1];
            int end = start + length;
            builder.append(str.substring(lastEnd, start));
            builder.append(interpolate(str.substring(start, end)));
            lastEnd = pair[0] + pair[1];
        }

        return builder.toString();
    }

    /**
     * Identifies any possible interpolation locations (begins by looking for
     * "${").
     *
     * @param str
     *            string to look through for interpolation sites
     * @return list of indices where the interpolation sites begin
     */
    private static List<Integer> possibleLocations(String str) {
        List<Integer> possibleLocations = new ArrayList<Integer>();

        int lastIndex = -1;
        int index;
        while ((index = str.indexOf("${", lastIndex + 1)) >= 0) {
            if (lastIndex == index) {
                break;
            }
            possibleLocations.add(index);
            lastIndex = index;
        }

        return possibleLocations;
    }

    /**
     * Confirm interpolation sites by looking for a closing brace.
     *
     * @param str
     *            string to confirm interpolation sites from
     * @param possibleLocations
     *            list of string indicies indicating possible interpolation
     *            beginnings
     * @return list paired integers containing (starting location, length of
     *         interpolation string)
     */
    private static List<Integer[]> confirmedLocations(String str, List<Integer> possibleLocations) {
        List<Integer[]> confirmedLocations = new ArrayList<Integer[]>();

        Integer[] lastPair = { -1, -1 };
        for (Integer start : possibleLocations) {
            int length = 0;

            // Ignore any escaped \${}
            if (start != 0 && str.charAt(start - 1) == '\\') {
                continue;
            }

            // Determine the length and existence of a ${} block
            boolean found = false;
            for (int i = start; i < str.length(); i++) {
                length += 1;
                if (str.charAt(i) == '}') {
                    found = true;
                    break;
                }
            }

            // Don't add overlapping pairs -- this will keep "${ ${hi} }" from
            // being interpolated
            // technically it will wind up "${ ${hi}"
            if ((lastPair[0] + lastPair[1] < start) && found) {
                Integer[] pair = { start, length };
                confirmedLocations.add(pair);
            }
        }

        return confirmedLocations;
    }

    /**
     * Interpolates the macros contained in the interpolation braces.
     *
     * <b>NOTE:</b> for ease of tokenization, this expects each token to have a
     * space between each component <b><i>e.g.</i></b> "Time.now + 1d" rather
     * than "Time.now+1d" <br>
     * <br>
     * <b>TODO:</b> Proper tokenizing
     *
     * @param str
     *            interpolation string
     * @return interpolated string
     */
    private static String interpolate(String str) {
        String toInterpolate = str.substring(2, str.length() - 1); // Strip ${
                                                                   // and }
        List<String> tokens = Arrays.asList(toInterpolate.split(" "));

        StringBuilder builder = new StringBuilder();
        Iterator<String> iter = tokens.iterator();
        while (iter.hasNext()) {
            String token = iter.next();

            if (token.equals("Time.now")) {
                builder.append(handleTime(tokens, iter));
            } else {
                logger.warn("Unrecognized token: {}", token);
                builder.append(token);
            }
        }

        return builder.toString();
    }

    /**
     * Handles the Time.now macro
     *
     * @param tokens
     *            list of tokens
     * @param iter
     *            iterator used to iterate over the list of tokens
     * @return string containing the interpolated time token
     */
    private static String handleTime(List<String> tokens, Iterator<String> iter) {
        DateTime dt = new DateTime();

        // Add some amount
        if (iter.hasNext()) {
            String operationToken = iter.next();
            if (operationToken.equals("+") || operationToken.equals("-")) {
                if (iter.hasNext()) {
                    String quantityToken = iter.next(); // Get the magnitude to
                                                        // add or subtract

                    ReadablePeriod period = getTimePeriod(quantityToken);

                    if (operationToken.equals("-")) {
                        dt = dt.minus(period);
                    } else {
                        dt = dt.plus(period);
                    }
                } else {
                    logger.warn("Token '{}' not followed by a quantity", operationToken);
                }
            } else {
                logger.warn("Invalid token '{}', must be operator '+' or '-'", operationToken);
            }
        }

        return DATE_UTIL.formatDateTime(dt);
    }

    /**
     * Defines the magnitudes that can be added to the timestamp
     *
     * @param token
     *            token of form "[number][magnitude]" (ex. "1d")
     * @return integer indicating the magnitude of the date for the calendar
     *         system
     */
    public static ReadablePeriod getTimePeriod(String token) {
        String valString = token.substring(0, token.length() - 1);
        int value = Integer.parseInt(valString);
        char mag = token.charAt(token.length() - 1);

        ReadablePeriod period;

        switch (mag) {
        case 's':
            period = Seconds.seconds(value);
            break;
        case 'm':
            period = Minutes.minutes(value);
            break;
        case 'h':
            period = Hours.hours(value);
            break;
        case 'd':
            period = Days.days(value);
            break;
        case 'M':
            period = Months.months(value);
            break;
        case 'y':
            period = Years.years(value);
            break;
        default:
            logger.warn("Invalid date magnitude: {}. Defaulting to seconds.", mag);
            period = Seconds.seconds(value);
            break;
        }

        return period;
    }
}