blob: 7fd9480d613e0a679ab42cedd1519e338f95f627 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.peeco.impl;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import java.lang.reflect.Method;
import org.apache.peeco.api.Matching;
import java.util.*;
import java.util.concurrent.CompletionStage;
import org.apache.peeco.api.HttpHandler;
import org.apache.peeco.api.Request;
import org.apache.peeco.api.Response;
public class PeecoUtils
{
public static org.apache.peeco.api.HttpMethod mapHttpMethod(HttpMethod method)
{
switch (method.name())
{
case "GET":
return org.apache.peeco.api.HttpMethod.GET;
case "POST":
return org.apache.peeco.api.HttpMethod.POST;
case "PUT":
return org.apache.peeco.api.HttpMethod.PUT;
case "DELETE":
return org.apache.peeco.api.HttpMethod.DELETE;
case "OPTIONS":
return org.apache.peeco.api.HttpMethod.OPTIONS;
case "HEAD":
return org.apache.peeco.api.HttpMethod.HEAD;
case "TRACE":
return org.apache.peeco.api.HttpMethod.TRACE;
case "CONNECT":
return org.apache.peeco.api.HttpMethod.CONNECT;
case "PATCH":
return org.apache.peeco.api.HttpMethod.PATCH;
}
return null;
}
public static HttpHandlerInfo getMatchingHandler(HttpRequest nettyRequest, List<HttpHandlerInfo> infos) throws RuntimeException
{
List<HttpHandlerInfo> matchings = new ArrayList<>();
for (HttpHandlerInfo info : infos)
{
String handlerUrl = info.annotation.url();
String incomingUrl = nettyRequest.uri();
if (isMatching(info.annotation.matching(), handlerUrl, incomingUrl)
&& Arrays.asList(info.annotation.method()).contains(mapHttpMethod(nettyRequest.method())))
{
if (info.annotation.matching() == Matching.EXACT)
{
return info;
}
matchings.add(info);
}
}
if (matchings.size() > 1)
{
throw new RuntimeException("Multiple HttpHandlers were found for the incoming Netty Request URI: " + nettyRequest.uri() +
". Only one method is allowed.");
}
return matchings.isEmpty() ? null : matchings.get(0);
}
public static boolean isMatching(Matching matching, String configuredUrl, String incomingUrl)
{
//cut off query parameters
if (incomingUrl.contains("?"))
{
incomingUrl = incomingUrl.substring(0, incomingUrl.indexOf("?"));
}
if (matching == Matching.EXACT)
{
if (configuredUrl.equals(incomingUrl))
{
return true;
}
}
else if (matching == Matching.WILDCARD)
{
if (configuredUrl.startsWith("*"))
{
if (incomingUrl.endsWith(configuredUrl.substring(1)))
{
return true;
}
}
else if (configuredUrl.endsWith("*"))
{
if (incomingUrl.startsWith(configuredUrl.substring(0, configuredUrl.length() - 1)))
{
return true;
}
}
}
return false;
}
public static List<HttpHandlerInfo> collectInfos(Class clazz) throws RuntimeException
{
ArrayList<HttpHandlerInfo> infos = new ArrayList<>();
for (Method method : clazz.getDeclaredMethods())
{
if (method.isAnnotationPresent(HttpHandler.class))
{
Class type = method.getDeclaringClass();
HttpHandler annotation = method.getAnnotation(HttpHandler.class);
boolean isRequestInParameterTypes = false;
for (Class parameterType : method.getParameterTypes())
{
if (parameterType.equals(Request.class))
{
isRequestInParameterTypes = true;
}
}
if (annotation != null
&& (method.getReturnType() == Response.class || method.getReturnType() == CompletionStage.class)
&& isRequestInParameterTypes)
{
infos.add(new HttpHandlerInfo(type, method, annotation));
}
else
{
throw new RuntimeException("Invalid method signature: " + method + ". The return type of the annotated method must be " +
Response.class.toString() + " or " + CompletionStage.class.toString() + "<Response>. The method signature must contain " +
Request.class.toString() + ".");
}
}
}
//Matching.EXACT should be first in the list
Comparator<HttpHandlerInfo> infoMatchingComparator = Comparator.comparing((HttpHandlerInfo info) -> info.annotation.matching());
infos.sort(infoMatchingComparator);
return infos;
}
}