diff --git a/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java b/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java index d14ef83259ec..24e06e662d74 100644 --- a/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java +++ b/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java @@ -16,6 +16,13 @@ package com.intellij.ide; import com.intellij.openapi.components.ServiceManager; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.Map; public interface XmlRpcServer { void addHandler(String name, Object handler); @@ -24,6 +31,8 @@ public interface XmlRpcServer { void removeHandler(String name); + boolean process(@NotNull String path, @NotNull HttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map handlers) throws IOException; + final class SERVICE { private SERVICE() { } diff --git a/platform/platform-api/src/org/jetbrains/ide/CustomPortServerManager.java b/platform/platform-api/src/org/jetbrains/ide/CustomPortServerManager.java index dc97a41932b5..2fb48795b7a8 100644 --- a/platform/platform-api/src/org/jetbrains/ide/CustomPortServerManager.java +++ b/platform/platform-api/src/org/jetbrains/ide/CustomPortServerManager.java @@ -18,6 +18,8 @@ package org.jetbrains.ide; import com.intellij.openapi.extensions.ExtensionPointName; import org.jetbrains.annotations.Nullable; +import java.util.Map; + public abstract class CustomPortServerManager { public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.customPortServerManager"); @@ -34,4 +36,11 @@ public abstract class CustomPortServerManager { public abstract boolean isAvailableExternally(); public abstract void setManager(@Nullable CustomPortService manager); + + /** + * This server will accept only XML-RPC requests if this method returns not-null map of XMl-RPC handlers + */ + public Map createXmlRpcHandlers() { + return null; + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java b/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java index afe91580436a..8e4f6a20e703 100644 --- a/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/XmlRpcServerImpl.java @@ -17,6 +17,7 @@ package com.intellij.ide; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.vfs.CharsetToolkit; import gnu.trove.THashMap; import org.apache.xmlrpc.*; import org.jboss.netty.buffer.ChannelBuffer; @@ -33,47 +34,23 @@ import org.jetbrains.ide.HttpRequestHandler; import org.jetbrains.io.Responses; import java.io.IOException; -import java.util.Arrays; +import java.util.Map; public class XmlRpcServerImpl implements XmlRpcServer { private static final Logger LOG = Logger.getInstance(XmlRpcServerImpl.class); - private final XmlRpcHandlerMappingImpl handlerMapping; - // idea doesn't use authentication - private final XmlRpcContext xmlRpcContext = new XmlRpcContext() { - @Nullable - @Override - public String getUserName() { - return null; - } - - @Nullable - @Override - public String getPassword() { - return null; - } - - @Override - public XmlRpcHandlerMapping getHandlerMapping() { - return handlerMapping; - } - }; + private final Map handlerMapping; public XmlRpcServerImpl() { - handlerMapping = LOG.isDebugEnabled() ? new LoggingDefaultHandlerMapping() : new XmlRpcHandlerMappingImpl(); - + handlerMapping = new THashMap(); for (XmlRpcHandlerBean handlerBean : Extensions.getExtensions(XmlRpcHandlerBean.EP_NAME)) { - final Object handler; try { - handler = handlerBean.instantiate(); + handlerMapping.put(handlerBean.name, handlerBean.instantiate()); } catch (ClassNotFoundException e) { LOG.error(e); - continue; } - handlerMapping.addHandler(handlerBean.name, handler); } - LOG.debug("XmlRpcServerImpl instantiated, handlers " + handlerMapping); } @@ -85,27 +62,28 @@ public class XmlRpcServerImpl implements XmlRpcServer { @Override public boolean process(QueryStringDecoder urlDecoder, HttpRequest request, ChannelHandlerContext context) throws IOException { - return ((XmlRpcServerImpl)SERVICE.getInstance()).process(urlDecoder, request, context); + return SERVICE.getInstance().process(urlDecoder.getPath(), request, context, null); } } @Override public boolean hasHandler(String name) { - return handlerMapping.handlers.containsKey(name); + return handlerMapping.containsKey(name); } @Override public void addHandler(String name, Object handler) { - handlerMapping.addHandler(name, handler); + handlerMapping.put(name, handler); } @Override public void removeHandler(String name) { - handlerMapping.removeHandler(name); + handlerMapping.remove(name); } - private boolean process(QueryStringDecoder urlDecoder, HttpRequest request, ChannelHandlerContext context) throws IOException { - if (!isXmlRpcRequest(urlDecoder.getPath())) { + @Override + public boolean process(@NotNull String path, @NotNull HttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map handlers) throws IOException { + if (!(path.isEmpty() || (path.length() == 1 && path.charAt(0) == '/') || path.equalsIgnoreCase("/RPC2"))) { return false; } @@ -113,11 +91,13 @@ public class XmlRpcServerImpl implements XmlRpcServer { ChannelBuffer result; ChannelBufferInputStream in = new ChannelBufferInputStream(request.getContent()); try { - result = ChannelBuffers.copiedBuffer(new XmlRpcWorker(handlerMapping).execute(in, xmlRpcContext)); + XmlRpcServerRequest xmlRpcServerRequest = new XmlRpcRequestProcessor().decodeRequest(in); + Object response = invokeHandler(getHandler(xmlRpcServerRequest.getMethodName(), handlers == null ? handlerMapping : handlers), xmlRpcServerRequest); + result = ChannelBuffers.copiedBuffer(new XmlRpcResponseProcessor().encodeResponse(response, CharsetToolkit.UTF8)); } - catch (Throwable ex) { + catch (Throwable e) { context.getChannel().close(); - LOG.error(ex); + LOG.error(e); return true; } finally { @@ -137,80 +117,32 @@ public class XmlRpcServerImpl implements XmlRpcServer { return false; } - private static boolean isXmlRpcRequest(String path) { - return path.isEmpty() || (path.length() == 1 && path.charAt(0) == '/') || path.equalsIgnoreCase("/RPC2"); + private static Object getHandler(String methodName, Map 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; + } + + IllegalStateException exception; + if (dot > -1) { + exception = new IllegalStateException("RPC handler object \"" + handlerName + "\" not found"); + } + else { + exception = new IllegalStateException("RPC handler object not found for \"" + methodName); + } + + LOG.error(exception); + throw exception; } - private static class XmlRpcHandlerMappingImpl implements XmlRpcHandlerMapping { - protected final THashMap handlers = new THashMap(); - - public void addHandler(@NotNull String handlerName, @NotNull Object handler) { - if (handler instanceof XmlRpcHandler) { - handlers.put(handlerName, handler); - } - else { - handlers.put(handlerName, new Invoker(handler)); - } - } - - public void removeHandler(String handlerName) { - handlers.remove(handlerName); - } - - @Override - public Object getHandler(String methodName) { - 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; - } - - IllegalStateException exception; - if (dot > -1) { - exception = new IllegalStateException("RPC handler object \"" + handlerName + "\" not found"); - } - else { - exception = new IllegalStateException("RPC handler object not found for \"" + methodName); - } - - LOG.error(exception); - throw exception; - } - } - - private static class LoggingDefaultHandlerMapping extends XmlRpcHandlerMappingImpl { - @Override - public void addHandler(@NotNull String handlerName, @NotNull Object handler) { - LOG.debug(String.format("addHandler: handlerName: %s, handler: %s%s", handlerName, handler, getHandlers())); - super.addHandler(handlerName, handler); - } - - @Override - public void removeHandler(String handlerName) { - LOG.debug(String.format("removeHandler: handlerName: %s%s", handlerName, getHandlers())); - super.removeHandler(handlerName); - } - - @Override - public Object getHandler(String methodName) { - LOG.debug(String.format("getHandler: methodName: %s%s", methodName, getHandlers())); - return super.getHandler(methodName); - } - - private String getHandlers() { - //noinspection SpellCheckingInspection - return String.format("%nhandlers: %s %s", Arrays.toString(handlers.keySet().toArray()), Arrays.toString(handlers.values().toArray())); - } - - @Override - public String toString() { - return getHandlers(); - } + private static Object invokeHandler(@NotNull Object handler, XmlRpcServerRequest request) throws Exception { + return (handler instanceof XmlRpcHandler ? (XmlRpcHandler)handler : new Invoker(handler)).execute(request.getMethodName(), request.getParameters()); } } \ No newline at end of file diff --git a/platform/platform-impl/src/org/jetbrains/ide/BuiltInServerManagerImpl.java b/platform/platform-impl/src/org/jetbrains/ide/BuiltInServerManagerImpl.java index a720565333d4..18fdc01588ee 100644 --- a/platform/platform-impl/src/org/jetbrains/ide/BuiltInServerManagerImpl.java +++ b/platform/platform-impl/src/org/jetbrains/ide/BuiltInServerManagerImpl.java @@ -37,7 +37,7 @@ public class BuiltInServerManagerImpl extends BuiltInServerManager { @Nullable private BuiltInServer server; - private boolean myEnabledInUnitTestMode = true; + private boolean enabledInUnitTestMode = true; @Override public int getPort() { @@ -83,7 +83,7 @@ public class BuiltInServerManagerImpl extends BuiltInServerManager { private Future startServerInPooledThread() { Application application = ApplicationManager.getApplication(); - if (application.isUnitTestMode() && !myEnabledInUnitTestMode) { + if (application.isUnitTestMode() && !enabledInUnitTestMode) { return null; } @@ -153,7 +153,6 @@ public class BuiltInServerManagerImpl extends BuiltInServerManager { */ @TestOnly public void setEnabledInUnitTestMode(boolean enabled) { - myEnabledInUnitTestMode = enabled; + enabledInUnitTestMode = enabled; } - } \ No newline at end of file diff --git a/platform/platform-impl/src/org/jetbrains/io/BuiltInServer.java b/platform/platform-impl/src/org/jetbrains/io/BuiltInServer.java index f38367b3c10a..f3e04dec5359 100644 --- a/platform/platform-impl/src/org/jetbrains/io/BuiltInServer.java +++ b/platform/platform-impl/src/org/jetbrains/io/BuiltInServer.java @@ -15,6 +15,7 @@ */ package org.jetbrains.io; +import com.intellij.ide.XmlRpcServer; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; @@ -23,17 +24,19 @@ import org.jboss.netty.channel.*; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.codec.http.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.ide.CustomPortServerManager; import org.jetbrains.ide.PooledThreadExecutor; +import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.util.Map; import java.util.concurrent.Executor; -import static org.jboss.netty.channel.Channels.pipeline; - public class BuiltInServer implements Disposable { private final ChannelGroup openChannels = new DefaultChannelGroup(); @@ -63,17 +66,34 @@ public class BuiltInServer implements Disposable { throw new IllegalStateException("server already started"); } - ServerBootstrap bootstrap = createServerBootstrap(channelFactory, openChannels); + ServerBootstrap bootstrap = createServerBootstrap(channelFactory, openChannels, null); int port = bind(firstPort, portsCount, tryAnyPort, bootstrap); bindCustomPorts(firstPort, port); return port; } - static ServerBootstrap createServerBootstrap(NioServerSocketChannelFactory channelFactory, ChannelGroup openChannels) { + static ServerBootstrap createServerBootstrap(NioServerSocketChannelFactory channelFactory, ChannelGroup openChannels, @Nullable Map xmlRpcHandlers) { ServerBootstrap bootstrap = new ServerBootstrap(channelFactory); bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.keepAlive", true); - bootstrap.setPipelineFactory(new ChannelPipelineFactoryImpl(new PortUnificationServerHandler(openChannels))); + if (xmlRpcHandlers == null) { + final ChannelHandler handler = new PortUnificationServerHandler(openChannels); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(handler); + } + }); + } + else { + final XmlRpcDelegatingHttpRequestHandler handler = new XmlRpcDelegatingHttpRequestHandler(xmlRpcHandlers); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(new HttpRequestDecoder(), new HttpChunkAggregator(1048576), new HttpResponseEncoder(), handler); + } + }); + } return bootstrap; } @@ -157,16 +177,17 @@ public class BuiltInServer implements Disposable { context.getPipeline().replace(DelegatingHttpRequestHandler.class, "replacedDefaultHandler", messageChannelHandler); } - private static class ChannelPipelineFactoryImpl implements ChannelPipelineFactory { - private final ChannelHandler defaultHandler; + private static final class XmlRpcDelegatingHttpRequestHandler extends DelegatingHttpRequestHandlerBase { + private final Map handlers; - public ChannelPipelineFactoryImpl(ChannelHandler defaultHandler) { - this.defaultHandler = defaultHandler; + public XmlRpcDelegatingHttpRequestHandler(Map handlers) { + this.handlers = handlers; } @Override - public ChannelPipeline getPipeline() throws Exception { - return pipeline(defaultHandler); + protected boolean process(ChannelHandlerContext context, HttpRequest request, QueryStringDecoder urlDecoder) throws IOException { + return (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.OPTIONS) && + XmlRpcServer.SERVICE.getInstance().process(urlDecoder.getPath(), request, context, handlers); } } } \ No newline at end of file diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.java b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.java index b189660c2790..560fc447f4ff 100644 --- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.java +++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.java @@ -19,8 +19,10 @@ import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.util.IconLoader; import com.intellij.util.ui.UIUtil; import org.apache.sanselan.ImageFormat; +import org.apache.sanselan.ImageWriteException; import org.apache.sanselan.Sanselan; -import org.jboss.netty.channel.*; +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.QueryStringDecoder; import org.jetbrains.ide.BuiltInServerManager; @@ -28,29 +30,16 @@ import org.jetbrains.ide.HttpRequestHandler; import javax.swing.*; import java.awt.image.BufferedImage; - -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; +import java.io.IOException; @ChannelHandler.Sharable -final class DelegatingHttpRequestHandler extends SimpleChannelUpstreamHandler { +final class DelegatingHttpRequestHandler extends DelegatingHttpRequestHandlerBase { @Override - public void messageReceived(ChannelHandlerContext context, MessageEvent event) throws Exception { - if (!(event.getMessage() instanceof HttpRequest)) { - context.sendUpstream(event); - return; - } - - HttpRequest request = (HttpRequest)event.getMessage(); - //if (BuiltInServer.LOG.isDebugEnabled()) { - //BuiltInServer.LOG.debug(request.toString()); - //} - - QueryStringDecoder urlDecoder = new QueryStringDecoder(request.getUri()); - + protected boolean process(ChannelHandlerContext context, HttpRequest request, QueryStringDecoder urlDecoder) throws IOException, ImageWriteException { HttpRequestHandler connectedHandler = (HttpRequestHandler)context.getAttachment(); if (connectedHandler != null) { if (connectedHandler.isSupported(request) && connectedHandler.process(urlDecoder, request, context)) { - return; + return true; } // prev cached connectedHandler is not suitable for this request, so, let's find it again context.setAttachment(null); @@ -63,7 +52,7 @@ final class DelegatingHttpRequestHandler extends SimpleChannelUpstreamHandler { icon.paintIcon(null, image.getGraphics(), 0, 0); byte[] icoBytes = Sanselan.writeImageToBytes(image, ImageFormat.IMAGE_FORMAT_ICO, null); Responses.send(icoBytes, FileResponses.createResponse(urlDecoder.getPath()), request, context); - return; + return true; } } @@ -73,24 +62,13 @@ final class DelegatingHttpRequestHandler extends SimpleChannelUpstreamHandler { if (context.getAttachment() == null) { context.setAttachment(handler); } - return; + return true; } } catch (Throwable e) { BuiltInServer.LOG.error(e); } } - Responses.sendStatus(request, context, NOT_FOUND); - } - - @Override - public void exceptionCaught(ChannelHandlerContext context, ExceptionEvent event) throws Exception { - try { - BuiltInServer.LOG.error(event.getCause()); - } - finally { - context.setAttachment(null); - event.getChannel().close(); - } + return false; } } diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java new file mode 100644 index 000000000000..682b4ced836f --- /dev/null +++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.java @@ -0,0 +1,42 @@ +package org.jetbrains.io; + +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.QueryStringDecoder; + +import static org.jboss.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; + +abstract class DelegatingHttpRequestHandlerBase extends SimpleChannelUpstreamHandler { + @Override + public final void messageReceived(ChannelHandlerContext context, MessageEvent event) throws Exception { + if (!(event.getMessage() instanceof HttpRequest)) { + context.sendUpstream(event); + return; + } + + HttpRequest request = (HttpRequest)event.getMessage(); + //if (BuiltInServer.LOG.isDebugEnabled()) { + //BuiltInServer.LOG.debug(request.toString()); + //} + + if (!process(context, request, new QueryStringDecoder(request.getUri()))) { + Responses.sendStatus(request, context, NOT_FOUND); + } + } + + protected abstract boolean process(ChannelHandlerContext context, HttpRequest request, QueryStringDecoder urlDecoder) throws Exception; + + @Override + public final void exceptionCaught(ChannelHandlerContext context, ExceptionEvent event) throws Exception { + try { + BuiltInServer.LOG.error(event.getCause()); + } + finally { + context.setAttachment(null); + event.getChannel().close(); + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/org/jetbrains/io/SubServer.java b/platform/platform-impl/src/org/jetbrains/io/SubServer.java index 663f9de2cfb5..cae43b9e18a7 100644 --- a/platform/platform-impl/src/org/jetbrains/io/SubServer.java +++ b/platform/platform-impl/src/org/jetbrains/io/SubServer.java @@ -34,7 +34,7 @@ final class SubServer implements CustomPortServerManager.CustomPortService, Disp public SubServer(CustomPortServerManager user, NioServerSocketChannelFactory channelFactory) { this.user = user; user.setManager(this); - bootstrap = BuiltInServer.createServerBootstrap(channelFactory, openChannels); + bootstrap = BuiltInServer.createServerBootstrap(channelFactory, openChannels, user.createXmlRpcHandlers()); } public boolean bind(int port) { @@ -59,7 +59,6 @@ final class SubServer implements CustomPortServerManager.CustomPortService, Disp } private void stop() { - // todo should we call releaseExternalResources? We use only 1 boss&worker thread openChannels.close().awaitUninterruptibly(); openChannels.clear(); }