cdce90716e3ba961499c15dca859395c116b1333
[idea/community.git] / platform / platform-impl / src / org / jetbrains / ide / RestService.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.ide;
17
18 import com.google.gson.stream.JsonReader;
19 import com.google.gson.stream.MalformedJsonException;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.project.ProjectManager;
23 import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
24 import com.intellij.openapi.vfs.CharsetToolkit;
25 import com.intellij.openapi.wm.IdeFocusManager;
26 import com.intellij.openapi.wm.IdeFrame;
27 import com.intellij.util.ExceptionUtil;
28 import com.intellij.util.containers.ContainerUtil;
29 import io.netty.buffer.ByteBufInputStream;
30 import io.netty.buffer.Unpooled;
31 import io.netty.channel.ChannelHandlerContext;
32 import io.netty.handler.codec.http.*;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.io.Responses;
36
37 import java.io.IOException;
38 import java.io.InputStreamReader;
39 import java.util.List;
40
41 /**
42  * Document your service using <a href="http://apidocjs.com">apiDoc</a>. To extract big example from source code, consider to use *.coffee file near your source file.
43  * (or Python/Ruby, but coffee recommended because it's plugin is lightweight). See {@link AboutHttpService} for example.
44  */
45 public abstract class RestService extends HttpRequestHandler {
46   protected static final Logger LOG = Logger.getInstance(RestService.class);
47
48   @Override
49   public final boolean isSupported(@NotNull FullHttpRequest request) {
50     if (!isMethodSupported(request.method())) {
51       return false;
52     }
53
54     String prefix = "rest";
55     String uri = request.uri();
56     String serviceName = getServiceName();
57     int minLength = 1 + prefix.length() + 1 + serviceName.length();
58     if (uri.length() >= minLength &&
59         uri.charAt(0) == '/' &&
60         uri.regionMatches(true, 1, prefix, 0, prefix.length()) &&
61         uri.regionMatches(true, 2 + prefix.length(), serviceName, 0, serviceName.length())) {
62       if (uri.length() == minLength) {
63         return true;
64       }
65       else {
66         char c = uri.charAt(minLength);
67         return c == '/' || c == '?';
68       }
69     }
70     return false;
71   }
72
73   @NotNull
74   /**
75    * Use human-readable name or UUID if it is an internal service.
76    */
77   protected abstract String getServiceName();
78
79   protected abstract boolean isMethodSupported(@NotNull HttpMethod method);
80
81   @Override
82   public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
83     try {
84       String error = execute(urlDecoder, request, context);
85       if (error != null) {
86         Responses.sendStatus(HttpResponseStatus.BAD_REQUEST, context.channel(), error, request);
87       }
88     }
89     catch (Throwable e) {
90       HttpResponseStatus status;
91       // JsonReader exception
92       //noinspection InstanceofCatchParameter
93       if (e instanceof MalformedJsonException || (e instanceof IllegalStateException && e.getMessage().startsWith("Expected a "))) {
94         LOG.warn(e);
95         status = HttpResponseStatus.BAD_REQUEST;
96       }
97       else {
98         LOG.error(e);
99         status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
100       }
101       Responses.sendStatus(status, context.channel(), ExceptionUtil.getThrowableText(e), request);
102     }
103     return true;
104   }
105
106   @Nullable("error text or null if successful")
107   /**
108    * Return error or send response using {@link #sendOk(FullHttpRequest, ChannelHandlerContext)}, {@link #send(BufferExposingByteArrayOutputStream, FullHttpRequest, ChannelHandlerContext)}
109    */
110   public abstract String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException;
111
112   @NotNull
113   protected static JsonReader createJsonReader(@NotNull FullHttpRequest request) {
114     JsonReader reader = new JsonReader(new InputStreamReader(new ByteBufInputStream(request.content()), CharsetToolkit.UTF8_CHARSET));
115     reader.setLenient(true);
116     return reader;
117   }
118
119   @Nullable
120   protected static Project guessProject() {
121     IdeFrame lastFocusedFrame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
122     Project project = lastFocusedFrame == null ? null : lastFocusedFrame.getProject();
123     if (project == null) {
124       Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
125       return openProjects.length > 0 ? openProjects[0] : null;
126     }
127     return project;
128   }
129
130   protected static void sendOk(@NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
131     Responses.send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK), context.channel(), request);
132   }
133
134   protected static void send(@NotNull BufferExposingByteArrayOutputStream byteOut, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
135     HttpResponse response = Responses.response("application/json", Unpooled.wrappedBuffer(byteOut.getInternalBuffer(), 0, byteOut.size()));
136     Responses.addNoCache(response);
137     Responses.send(response, context.channel(), request);
138   }
139
140   protected static boolean getBooleanParameter(@NotNull String name, @NotNull QueryStringDecoder urlDecoder) {
141     List<String> values = urlDecoder.parameters().get(name);
142     if (ContainerUtil.isEmpty(values)) {
143       return false;
144     }
145
146     String value = values.get(values.size() - 1);
147     // if just name specified, so, true
148     return value.isEmpty() || Boolean.parseBoolean(value);
149   }
150 }