/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright © 2011 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.repo.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.forgerock.json.resource.BadRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TokenHandler {
final static Logger logger = LoggerFactory.getLogger(TokenHandler.class);
// The OpenIDM query token is of format ${token-name}
Pattern tokenPattern = Pattern.compile("\\$\\{(.+?)\\}");
/**
* Replaces a query string with tokens of format ${token-name} with the values from the
* passed in map, where the token-name must be the key in the map
*
* @param queryString the query with tokens
* @param params the parameters to replace the tokens. Values can be String or List.
* @return the query with all tokens replace with their found values
* @throws BadRequestException if token in the query is not in the passed parameters
*/
public String replaceTokensWithValues(String queryString, Map<String, Object> params)
throws BadRequestException {
java.util.regex.Matcher matcher = tokenPattern.matcher(queryString);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
String tokenKey = matcher.group(1);
if (!params.containsKey(tokenKey)) {
// fail with an exception if token not found
throw new BadRequestException("Missing entry in params passed to query for token " + tokenKey);
} else {
Object replacement = params.get(tokenKey);
if (replacement instanceof List) {
StringBuffer commaSeparated = new StringBuffer();
boolean first = true;
for (Object entry : ((List) replacement)) {
if (!first) {
commaSeparated.append(",");
} else {
first = false;
}
commaSeparated.append(entry.toString());
}
replacement = commaSeparated.toString();
}
if (replacement == null) {
replacement = "";
}
matcher.appendReplacement(buffer, "");
buffer.append(replacement);
}
}
matcher.appendTail(buffer);
return buffer.toString();
}
/**
* Replaces a query string with tokens of format ${token-name} with the
* specified replacement string for all tokens.
*
* @param queryString the query with tokens to replace
* @param replacement the replacement string
* @return the query with all tokens replaced
*/
public String replaceTokens(String queryString, String replacement) {
return replaceTokens(queryString, replacement, new String[] {});
}
/**
* Replaces a query string with tokens of format ${token-name} with the
* specified replacement string for all tokens.
*
* @param queryString the query with tokens to replace
* @param replacement the replacement string
* @param nonReplacementTokenPrefixes optional array of prefixes that, if found as part of a token,
* will not be replaced
* @return the query with all tokens replaced
*/
public String replaceTokens(String queryString, String replacement, String... nonReplacementTokenPrefixes) {
Matcher matcher = tokenPattern.matcher(queryString);
StringBuffer buf = new StringBuffer();
while (matcher.find()) {
String origToken = matcher.group(1);
//TODO: the size check seems invalid
if (origToken != null) {
// OrientDB token is of format :token-name
matcher.appendReplacement(buf, "");
// if token has one of the "non-replacement" prefixes, leave it alone
if (tokenStartsWithPrefix(origToken, nonReplacementTokenPrefixes)) {
buf.append("${" + origToken + "}");
}
else {
buf.append(replacement);
}
}
}
matcher.appendTail(buf);
return buf.toString();
}
/**
* Returns whether the token starts with one of the prefixes passed.
*
* @param token the token to interrogate
* @param prefixes a list of prefixes
* @return whether the passed token starts with one of the prefixes
*/
private boolean tokenStartsWithPrefix(String token, String... prefixes) {
String[] tokenParts = token.split(":", 2);
if (tokenParts.length == 2) {
for (String prefix : prefixes) {
if (prefix.equals(tokenParts[0])) {
return true;
}
}
}
return false;
}
/**
* Extracts all the token names in the query string of format ${token-name}
*
* @param queryString the query with tokens
* @return the list of token names, in the order they appear in the queryString
*/
public List<String> extractTokens(String queryString) {
List<String> tokens = new ArrayList<String>();
Matcher matcher = tokenPattern.matcher(queryString);
while (matcher.find()) {
String origToken = matcher.group(1);
tokens.add(origToken);
}
return tokens;
}
/**
* Replaces some tokens in a query string with tokens of format ${token-name}
* with the given replacements, which may again be tokens (e.g. in another format)
* or values. Tokens that have no replacement defined stay in the original token format.
*
*
* @param queryString the query with OpenIDM format tokens ${token}
* @param replacements the replacement values/tokens, where the key is the token name in the query string,
* and the value is the String to replace it with.
* @return the query with any defined replacement values/tokens replaced, and the remaining tokens
* left in the original format
*/
public String replaceSomeTokens(String queryString, Map<String, String> replacements) {
Matcher matcher = tokenPattern.matcher(queryString);
StringBuffer buf = new StringBuffer();
while (matcher.find()) {
String origToken = matcher.group(1);
if (origToken != null) {
String replacement = replacements.get(origToken);
if (replacement == null) {
// if not replacement specified, keep the original token.
replacement = "${" + origToken + "}";
}
matcher.appendReplacement(buf, "");
buf.append(replacement);
}
}
matcher.appendTail(buf);
return buf.toString();
}
/**
* Replaces some tokens in a query string with tokens of format ${token-name}
* where token-name represents a list of values. The numberOfReplacements Map tells
* how many replacements to produce (comma-separated) for each token. The replacement
* (for all tokens) is provided. Tokens that have no replacement defined stay in the
* original token format.
*
* @param queryString the query with OpenIDM format tokens ${token}
* @param numberOfReplacements the number of replacements to replace a ${token} with
* @param replacement the replacement values/tokens
* @return the query with any defined replacement values/tokens replaced, and the remaining tokens
* left in the original format
*/
public String replaceListTokens(String queryString, Map<String, Integer> numberOfReplacements, String replacement) {
Matcher matcher = tokenPattern.matcher(queryString);
StringBuffer buf = new StringBuffer();
while (matcher.find()) {
String origToken = matcher.group(1);
if (origToken != null) {
matcher.appendReplacement(buf, "");
Integer length = numberOfReplacements.get(origToken);
if (length != null) {
for (int i = 0; i < length; i++) {
buf.append(replacement);
if (i != length - 1) {
buf.append(", ");
}
}
}
else {
buf.append("${" + origToken + "}");
}
}
}
matcher.appendTail(buf);
return buf.toString();
}
/**
* Replaces a query string with tokens of format ${token-name} with the
* token format in OrientDB, which is of the form :token-name.
*
* OrientDB tokens has some limitations, e.g. they can currently only be used
* in the where clause, and hence the returned string is not guaranteed to be
* valid for use in a prepared statement. If the parsing fails the system may
* have to fall back onto non-prepared statements and manual token replacement.
*
* @param queryString the query with OpenIDM format tokens ${token}
* @return the query with all tokens replaced with the OrientDB style tokens :token
*/
public String replaceTokensWithOrientToken(String queryString) {
Matcher matcher = tokenPattern.matcher(queryString);
StringBuffer buf = new StringBuffer();
while (matcher.find()) {
String origToken = matcher.group(1);
//TODO: the size check seems invalid
if (origToken != null && origToken.length() > 3) {
// OrientDB token is of format :token-name
String newToken = ":" + origToken;
matcher.appendReplacement(buf, "");
buf.append(newToken);
}
}
matcher.appendTail(buf);
return buf.toString();
}
}