/* $HeadURL$ * $Id$ * * Copyright (c) 2006-2010 by Public Library of Science * http://plos.org * http://ambraproject.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ambraproject.util; import freemarker.ext.beans.ArrayModel; import freemarker.ext.beans.DateModel; import freemarker.ext.beans.NumberModel; import freemarker.ext.beans.StringModel; import freemarker.template.SimpleNumber; import freemarker.template.SimpleScalar; import freemarker.template.SimpleSequence; import freemarker.template.TemplateCollectionModel; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateModel; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateException; import freemarker.template.TemplateModelException; import freemarker.core.Environment; import freemarker.template.TemplateModelIterator; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Date; import java.util.Map; import java.io.IOException; /** * Created by IntelliJ IDEA. * User: josowski * Date: Apr 23, 2010 * Time: 11:25:58 AM * * This class can be used to generate URL strings from the set of variables stored in the * template model. * * If you specify a name/value pair as parameters to this directive you'll replace * existing values with the passed in value. * * If the name does not exist as part of the original collection, the new name/value pair * will not be added to the returned URL * * This currently handle arrays in a not intuitive way. If you specify an array/collection * variable name, the whole of the value applied will replace the array by default. * If you specifiy method='add' the new value will be appended to the list. * * The first parameter can also be a comma delimited list. * * The second parameter can also be a comma delimeted list. In such case an * attempt is made to match each value with the name of the same index. * If there are less values then names the last value will be applied to all * subsequent names */ public class URLParametersDirective implements TemplateDirectiveModel { private static final String UTF8 = "UTF-8"; public void execute(Environment environment, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { Object searchParams; Object values; String names; String method; if (params.isEmpty()) { throw new TemplateModelException( "URLParameterDirective is missing required parameters of parameters."); } else { searchParams = params.get("parameters"); names = params.get("names")==null?null:String.valueOf(params.get("names")); method = params.get("method")==null?null:String.valueOf(params.get("method")); values = params.get("values"); if(searchParams == null) { throw new TemplateModelException( "URLParameterDirective is missing required parameters of parameters."); } if(!(searchParams instanceof StringModel)) { throw new TemplateModelException( "URLParameterDirective searchParams parameter is not instance of StringModel."); } if(method == null) { method = "replace"; } else { if(!(method.equals("add") || method.equals("replace"))) { throw new TemplateModelException( "URLParameterDirective only accepts 'add' and 'replace' as variable methods."); } } } if (loopVars.length != 0) { throw new TemplateModelException( "URLParameterDirective doesn't allow loop variables."); } environment.getOut().write(makeURLParameters((StringModel)searchParams, names, values, method)); } /** * Takes two strings. If the second string is a comma delimited set, compare * each value of that set. * @param key * @param paramName * @return -1 if no match is found. 0 or greater if there is a match. If there is a match * this return value will reflect the index on which it is found. Will return 0 if the * parameter is not a string */ private int propertyNameCheck(String key, String paramName) { int res = -1; if(paramName != null) { String[] params = paramName.split(","); if(params.length > 0) { for(String p : params) { res++; if(key.equals(p)) { return res; } } return -1; } else { if(String.valueOf(key).equals(paramName)) { return 0; } else { return -1; } } } return -1; } /** * If this function is passed an array to get a value * Use that instead of the value parameter * Pair the value element with the passed index * If index is larger then the values list, use the last value in the list * @param values * @return */ private String getValue(int index, Object values) throws TemplateModelException { if(values != null) { if(values instanceof SimpleSequence) { String val = null; if(index < ((SimpleSequence)values).size()) { return getValue(((SimpleSequence) values).get(index)); } else { return getValue(((SimpleSequence) values).get(((SimpleSequence) values).size())); } } return getValue(values); } else { return ""; } } private String getValue(Object value) throws TemplateModelException { if((value instanceof NumberModel) || (value instanceof StringModel) || (value instanceof SimpleScalar || (value instanceof SimpleNumber))) { return String.valueOf(value); } if(value instanceof DateModel) { Date d = ((DateModel)value).getAsDate(); return DateParser.getIsoDateNoMillis(d); } throw new TemplateModelException("A bad value was given, values must be of type string, number, date" + " or collection in a format of: [\"value\",1,5]"); } public String makeURLParameters(StringModel params, String names, Object values, String method) throws TemplateModelException { TemplateCollectionModel tc = params.keys(); TemplateModelIterator keysIterator = params.keys().iterator(); StringBuilder sb = new StringBuilder(); while(keysIterator.hasNext()) { TemplateModel key = keysIterator.next(); //Lets ignore the class key if(!key.toString().equals("class")) { Object o = params.get(key.toString()); //It's either a collection or value if(o instanceof ArrayModel) { sb.append(key.toString()); sb.append("="); TemplateModelIterator arrayIt = ((ArrayModel)o).iterator(); int keyIndex = propertyNameCheck(String.valueOf(key),names); if(keyIndex > -1) { try { if(method.equals("replace")) { String val = getValue(keyIndex, values); sb.append(URLEncoder.encode(val,UTF8)); } if(method.equals("add")) { while(arrayIt.hasNext()) { sb.append(getValue(arrayIt.next())); sb.append(","); } String val = getValue(keyIndex, values); sb.append(URLEncoder.encode(val,UTF8)); } } catch (UnsupportedEncodingException ex) { throw new TemplateModelException("UnsupportedEncodingException, " + UTF8 + " not supported: ", ex); } } else { while(arrayIt.hasNext()) { sb.append(getValue(arrayIt.next())); if(arrayIt.hasNext()) { sb.append(","); } } } } else if((o instanceof NumberModel) || (o instanceof StringModel) || (o instanceof DateModel)) { sb.append(String.valueOf(key)); sb.append("="); try { int keyIndex = propertyNameCheck(String.valueOf(key),names); if(keyIndex > -1) { String val = getValue(keyIndex, values); sb.append(URLEncoder.encode(val, UTF8)); } else { sb.append(URLEncoder.encode(getValue(o), UTF8)); } } catch (UnsupportedEncodingException ex) { throw new TemplateModelException("UnsupportedEncodingException, " + UTF8 + " not supported: ", ex); } } //If the object matches any of the preceding cases, add a "&" if((o instanceof ArrayModel) || (o instanceof NumberModel) || (o instanceof StringModel) || o instanceof DateModel) { if(keysIterator.hasNext()) { sb.append("&"); } } } } return sb.toString(); } }