blob: 3b8bacee5457a796eedc91af2566fa3d3cf5bb9c [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.ide;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import gnu.trove.THashMap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.apache.xmlrpc.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.HttpRequestHandler;
import org.jetbrains.io.Responses;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Vector;
public class XmlRpcServerImpl implements XmlRpcServer {
private static final Logger LOG = Logger.getInstance(XmlRpcServerImpl.class);
private final Map<String, Object> handlerMapping;
public XmlRpcServerImpl() {
handlerMapping = new THashMap<String, Object>();
for (XmlRpcHandlerBean handlerBean : Extensions.getExtensions(XmlRpcHandlerBean.EP_NAME)) {
try {
handlerMapping.put(handlerBean.name, handlerBean.instantiate());
}
catch (ClassNotFoundException e) {
LOG.error(e);
}
}
LOG.debug("XmlRpcServerImpl instantiated, handlers " + handlerMapping);
}
static final class XmlRpcRequestHandler extends HttpRequestHandler {
@Override
public boolean isSupported(@NotNull FullHttpRequest request) {
return request.method() == HttpMethod.POST || request.method() == HttpMethod.OPTIONS;
}
@Override
public boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
return SERVICE.getInstance().process(urlDecoder.path(), request, context, null);
}
}
@Override
public boolean hasHandler(String name) {
return handlerMapping.containsKey(name);
}
@Override
public void addHandler(String name, Object handler) {
handlerMapping.put(name, handler);
}
@Override
public void removeHandler(String name) {
handlerMapping.remove(name);
}
@Override
public boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map<String, Object> handlers) throws IOException {
if (!(path.isEmpty() || (path.length() == 1 && path.charAt(0) == '/') || path.equalsIgnoreCase("/rpc2"))) {
return false;
}
if (request.method() == HttpMethod.POST) {
ByteBuf result;
ByteBufInputStream in = new ByteBufInputStream(request.content());
try {
XmlRpcServerRequest xmlRpcServerRequest = new XmlRpcRequestProcessor().decodeRequest(in);
if (StringUtil.isEmpty(xmlRpcServerRequest.getMethodName())) {
LOG.warn("method name empty");
return false;
}
Object response = invokeHandler(getHandler(xmlRpcServerRequest.getMethodName(), handlers == null ? handlerMapping : handlers), xmlRpcServerRequest);
result = Unpooled.copiedBuffer(new XmlRpcResponseProcessor().encodeResponse(response, CharsetToolkit.UTF8));
}
catch (Throwable e) {
context.channel().close();
LOG.error(e);
return true;
}
finally {
in.close();
}
Responses.send(Responses.response("text/xml", result), context.channel(), request);
return true;
}
else if (HttpMethod.POST.name().equals(request.headers().get("Access-Control-Request-Method"))) {
LOG.assertTrue(request.method() == HttpMethod.OPTIONS);
Responses.sendOptionsResponse("POST, OPTIONS", request, context);
return true;
}
return false;
}
private static Object getHandler(@NotNull String methodName, @NotNull Map<String, Object> handlers) {
Object handler = null;
String handlerName = null;
int dot = methodName.lastIndexOf('.');
if (dot > -1) {
handlerName = methodName.substring(0, dot);
handler = handlers.get(handlerName);
}
if (handler != null) {
return handler;
}
if (dot > -1) {
throw new IllegalStateException("RPC handler object \"" + handlerName + "\" not found");
}
else {
throw new IllegalStateException("RPC handler object not found for \"" + methodName);
}
}
private static Object invokeHandler(@NotNull Object handler, XmlRpcServerRequest request) throws Throwable {
return handler instanceof XmlRpcHandler ? (XmlRpcHandler)handler : invoke(handler, request.getMethodName(), request.getParameters());
}
private static Object invoke(Object target, String methodName, @SuppressWarnings("UseOfObsoleteCollectionType") Vector params) throws Throwable {
Class<?> targetClass = (target instanceof Class) ? (Class) target : target.getClass();
Class[] argClasses = null;
Object[] argValues = null;
if (params != null) {
argClasses = new Class[params.size()];
argValues = new Object[params.size()];
for (int i = 0; i < params.size(); i++) {
argValues[i] = params.elementAt(i);
if (argValues[i] instanceof Integer) {
argClasses[i] = Integer.TYPE;
}
else if (argValues[i] instanceof Double) {
argClasses[i] = Double.TYPE;
}
else if (argValues[i] instanceof Boolean) {
argClasses[i] = Boolean.TYPE;
}
else {
argClasses[i] = argValues[i].getClass();
}
}
}
Method method;
int dot = methodName.lastIndexOf('.');
if (dot > -1 && dot + 1 < methodName.length()) {
methodName = methodName.substring(dot + 1);
}
method = targetClass.getMethod(methodName, argClasses);
// Our policy is to make all public methods callable except the ones defined in java.lang.Object
if (method.getDeclaringClass() == Object.class) {
throw new XmlRpcException(0, "Invoker can't call methods defined in java.lang.Object");
}
Object returnValue = method.invoke(target, argValues);
if (returnValue == null && method.getReturnType() == Void.TYPE) {
// Not supported by the spec.
throw new IllegalArgumentException("void return types for handler methods not supported, " + methodName);
}
return returnValue;
}
}