package com.mastfrog.acteur.mongo.util; import com.google.inject.Inject; import com.google.inject.Provider; import com.mastfrog.acteur.HttpEvent; import com.mongodb.BasicDBObject; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bson.types.ObjectId; /** * Takes the current HTTP request's URL parameters and converts them to a * BasicDBObject which can be used as a mongodb query. In particular, uses a * syntax where numeric properties can specify greater than, less than, * greater-than-or-equal or less-than-or-equal, e.g. * /users/foo/time?start=>=2505050 * <p/> * Also extracts comma-delimited values into a list of strings * * @author Tim Boudreau */ public final class EventToQuery implements Provider<BasicDBObject> { private final Provider<HttpEvent> provider; private static final String _id = "_id"; private final EventToQueryConfig config; @Inject public EventToQuery(Provider<HttpEvent> provider, EventToQueryConfig config) { this.provider = provider; this.config = config; } @Override public BasicDBObject get() { HttpEvent evt = provider.get(); BasicDBObject obj = new BasicDBObject(); for (String param : config) { boolean found = false; String v = evt.getParameter(param); if (v == null) { continue; } try { v = URLDecoder.decode(v, "UTF-8"); } catch (UnsupportedEncodingException ex) { throw new InvalidParameterException("UTF-8 not supported - WTF?"); } for (Patterns p : Patterns.values()) { if (p.process(obj, param, v)) { found = true; break; } } if (!found) { if (v != null) { try { long val = Long.parseLong(v); obj.put(param, val); } catch (NumberFormatException ex) { InvalidParameterException rethrow = new InvalidParameterException( "Parameter " + param + " is not a number: " + v); rethrow.addSuppressed(ex); throw rethrow; } } } } for (Map.Entry<String, String> e : evt.getParametersAsMap().entrySet()) { if (config.isIgnoredParameter(e.getKey()) || config.isNumericParameter(e.getKey())) { continue; } String v = e.getValue(); if (v.indexOf(',') > 0) { String[] spl = v.split(","); List<String> l = new LinkedList<>(); for (String s : spl) { l.add(s.trim()); } obj.append(e.getKey(), l); } else { obj.append(e.getKey(), v); } } obj = onQueryConstructed(evt, obj); return obj; } protected BasicDBObject onQueryConstructed(HttpEvent evt, BasicDBObject obj) { if (!obj.isEmpty()) { String idparam = evt.getParameter(_id); if (idparam != null) { try { obj.put(_id, new ObjectId(idparam)); } catch (IllegalArgumentException ex) { InvalidParameterException pex = new InvalidParameterException("Invalid id: " + idparam); pex.addSuppressed(ex); throw pex; } } } return config.onQueryConstructed(evt, obj); } private static enum Patterns { GTE(">[e=]([\\d\\-]+)$"), LTE("<[e=]([\\d\\-]+)$"), GT(">([\\d\\-]+)$"), LT("<([\\-\\d]+)$"); private final Pattern pattern; Patterns(String pattern) { this.pattern = Pattern.compile(pattern); } boolean process(BasicDBObject ob, String name, String val) { if (val != null) { try { val = URLDecoder.decode(val, "UTF-8"); } catch (UnsupportedEncodingException ex) { throw new InvalidParameterException("UTF-8 not supported - WTF?"); } return decorate(ob, name, val); } return false; } private Long get(String val) { Matcher m = pattern.matcher(val); boolean found = m.find(); if (found) { return Long.parseLong(m.group(1)); } return null; } boolean decorate(BasicDBObject ob, String name, String val) { try { Long value = get(val); if (value != null) { ob.put(name, new BasicDBObject(toString(), value)); return true; } } catch (NumberFormatException nfe) { InvalidParameterException rethrow = new InvalidParameterException( "Parameter " + name + " is not a number: " + val); rethrow.addSuppressed(nfe); throw rethrow; } return false; } @Override public String toString() { return '$' + name().toLowerCase(); } } public interface QueryDecorator { BasicDBObject onQueryConstructed(HttpEvent evt, BasicDBObject obj); } }