blob: 0c741575d7cdcced0be988ab71df6838a8d8ae3d [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.shardingsphere.elasticjob.restful.pipeline;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.QueryStringDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.elasticjob.restful.Http;
import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializer;
import org.apache.shardingsphere.elasticjob.restful.deserializer.RequestBodyDeserializerFactory;
import org.apache.shardingsphere.elasticjob.restful.handler.HandleContext;
import org.apache.shardingsphere.elasticjob.restful.handler.Handler;
import org.apache.shardingsphere.elasticjob.restful.handler.HandlerParameter;
import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
import org.apache.shardingsphere.elasticjob.restful.mapping.PathMatcher;
import org.apache.shardingsphere.elasticjob.restful.mapping.RegexPathMatcher;
import org.apache.shardingsphere.elasticjob.restful.wrapper.QueryParameterMap;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* This handler is used for preparing parameters before executing handle method.
* It prepares arguments declared by {@link org.apache.shardingsphere.elasticjob.restful.annotation.Param}
* and {@link org.apache.shardingsphere.elasticjob.restful.annotation.RequestBody}, and deserializes arguments to declared type.
*/
@Slf4j
@Sharable
public final class HandlerParameterDecoder extends ChannelInboundHandlerAdapter {
private final PathMatcher pathMatcher = new RegexPathMatcher();
@SuppressWarnings({"unchecked", "NullableProblems"})
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
HandleContext<Handler> handleContext = (HandleContext<Handler>) msg;
FullHttpRequest httpRequest = handleContext.getHttpRequest();
MappingContext<Handler> mappingContext = handleContext.getMappingContext();
Object[] arguments = prepareArguments(httpRequest, mappingContext);
handleContext.setArgs(arguments);
ctx.fireChannelRead(handleContext);
}
private Object[] prepareArguments(final FullHttpRequest httpRequest, final MappingContext<Handler> mappingContext) {
Handler handler = mappingContext.payload();
List<HandlerParameter> handlerParameters = handler.getHandlerParameters();
Map<String, List<String>> queryParameters = parseQuery(httpRequest.uri());
Map<String, String> templateVariables = pathMatcher.captureVariables(mappingContext.pattern(), httpRequest.uri());
Object[] result = new Object[handlerParameters.size()];
boolean requestBodyAlreadyParsed = false;
for (int i = 0; i < handlerParameters.size(); i++) {
HandlerParameter handlerParameter = handlerParameters.get(i);
Object parsedValue = null;
String parameterName = handlerParameter.getName();
Class<?> targetType = handlerParameter.getType();
boolean nullable = !handlerParameter.isRequired();
switch (handlerParameter.getParamSource()) {
case PATH:
String rawPathValue = templateVariables.get(parameterName);
Object parsedPathValue = deserializeBuiltInType(targetType, rawPathValue);
Preconditions.checkArgument(nullable || null != parsedPathValue, "Missing path variable [%s].", parameterName);
parsedValue = parsedPathValue;
break;
case QUERY:
List<String> rawQueryValues = queryParameters.get(parameterName);
Object parsedQueryValue = deserializeQueryParameter(targetType, rawQueryValues);
Preconditions.checkArgument(nullable || null != parsedQueryValue, "Missing query parameter [%s].", parameterName);
parsedValue = parsedQueryValue;
break;
case HEADER:
String rawHeaderValue = httpRequest.headers().get(parameterName);
Object parsedHeaderValue = deserializeBuiltInType(targetType, rawHeaderValue);
Preconditions.checkArgument(nullable || null != parsedHeaderValue, "Missing header value [%s].", parameterName);
parsedValue = parsedHeaderValue;
break;
case BODY:
Preconditions.checkState(!requestBodyAlreadyParsed, "@RequestBody duplicated on handle method.");
byte[] bytes = ByteBufUtil.getBytes(httpRequest.content());
String mimeType = Optional.ofNullable(HttpUtil.getMimeType(httpRequest))
.orElseGet(() -> HttpUtil.getMimeType(Http.DEFAULT_CONTENT_TYPE)).toString();
RequestBodyDeserializer deserializer = RequestBodyDeserializerFactory.getRequestBodyDeserializer(mimeType);
Object parsedBodyValue = deserializer.deserialize(targetType, bytes);
parsedValue = parsedBodyValue;
Preconditions.checkArgument(nullable || null != parsedBodyValue, "Missing request body");
requestBodyAlreadyParsed = true;
break;
case UNKNOWN:
if (QueryParameterMap.class.isAssignableFrom(targetType)) {
parsedValue = new QueryParameterMap(queryParameters);
} else {
log.warn("Unknown source argument [{}] on index [{}].", parameterName, handlerParameter.getIndex());
}
break;
default:
}
result[i] = parsedValue;
}
return result;
}
private Map<String, List<String>> parseQuery(final String uri) {
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);
return queryStringDecoder.parameters();
}
private Object deserializeQueryParameter(final Class<?> targetType, final List<String> queryValues) {
if (null == queryValues || queryValues.isEmpty()) {
return null;
}
if (1 == queryValues.size()) {
return deserializeBuiltInType(targetType, queryValues.get(0));
}
throw new UnsupportedOperationException("Multi value query doesn't support yet.");
}
private Object deserializeBuiltInType(final Class<?> targetType, final String value) {
Preconditions.checkArgument(!value.isEmpty(), "Cannot deserialize empty value.");
if (String.class.equals(targetType)) {
return value;
}
if (Boolean.class.equals(targetType) || boolean.class.equals(targetType)) {
return Boolean.parseBoolean(value);
}
if (Character.class.equals(targetType) || char.class.equals(targetType)) {
Preconditions.checkArgument(1 >= value.length(), MessageFormat.format("Cannot set value [{0}] into a char.", value));
return value.charAt(0);
}
if (Byte.class.equals(targetType) || byte.class.equals(targetType)) {
return Byte.parseByte(value);
}
if (Short.class.equals(targetType) || short.class.equals(targetType)) {
return Short.parseShort(value);
}
if (Integer.class.equals(targetType) || int.class.equals(targetType)) {
return Integer.parseInt(value);
}
if (Long.class.equals(targetType) || long.class.equals(targetType)) {
return Long.parseLong(value);
}
if (Float.class.equals(targetType) || float.class.equals(targetType)) {
return Float.parseFloat(value);
}
if (Double.class.equals(targetType) || double.class.equals(targetType)) {
return Double.parseDouble(value);
}
throw new IllegalArgumentException(MessageFormat.format("Cannot deserialize path variable [{0}] into [{1}]", value, targetType.getName()));
}
}