/* * Copyright (c) 2014. * * BaasBox - info-at-baasbox.com * * 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 com.baasbox.service.permissions; import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.baasbox.service.logging.BaasBoxLogger; import play.api.Routes; import play.mvc.Http; import scala.Option; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper for parsing annotations * Created by Andrea Tortorella on 08/04/14. */ public final class RouteTagger { private RouteTagger(){throw new AssertionError("uninstantiable");} // change to true to check route parse logging private static final boolean CHECK_PARSING = false; private static final Pattern COMMENT_ANNOTATION_PATTERN = Pattern.compile("@([a-zA-Z_$][a-zA-Z\\d_$]*)(\\((([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*)?\\))?"); private static final LoadingCache<String,Map<String,Set<String>>> PARSER_CACHE = CacheBuilder.newBuilder() .build(new CacheLoader<String, Map<String, Set<String>>>() { @Override public Map<String, Set<String>> load(String comment) throws Exception { if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("New route annotations to parse "); return parseComments(comment); } }); /** * Attaches route annotations to the context for the current request * @param context * @return the attached annotations map */ public static Map<String,Set<String>> attachAnnotations(Http.Context context){ final String routeComment = getComment(context); Map<String, Set<String>> annotations = parse(routeComment); context.args.putAll(annotations); Http.Context.current.set(context); return annotations; } @VisibleForTesting public static Map<String,Set<String>> parse(final String comment){ try { if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Parsing tags for current route with comment: "+comment); if (comment==null||comment.length()==0){ return Collections.emptyMap(); } if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Matched routes"); return PARSER_CACHE.get(comment); } catch (ExecutionException e){ if (BaasBoxLogger.isErrorEnabled())BaasBoxLogger.error("Error reading tags for current route with comment: "+comment); return Collections.emptyMap(); } } private static Map<String,Set<String>> parseComments(String comment){ HashMap<String,Set<String>> tags = new HashMap<String,Set<String>>(); Matcher m = COMMENT_ANNOTATION_PATTERN.matcher(comment); while (m.find()){ int groups = m.groupCount(); if (CHECK_PARSING) { BaasBoxLogger.info("Comment: " + comment); for (int i = 0; i < groups; i++) { BaasBoxLogger.info("group(" + i + "): " + m.group(i)); } } String name = m.group(1); String value = groups>2?m.group(3):name; if (value==null){ value=name; } Set<String> vals = tags.get(name); if (vals == null){ vals = new HashSet<String>(); tags.put(name,vals); } vals.add(value); } return tags; } private static String getComment(Http.Context context){ Option<String> opt = context._requestHeader().tags().get(Routes.ROUTE_COMMENTS()); return opt.isDefined()?opt.get():""; } }