001 /* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2013 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * SonarQube is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020 package org.sonar.api.server.ws; 021 022 import com.google.common.collect.ImmutableList; 023 import com.google.common.collect.ImmutableMap; 024 import com.google.common.collect.Maps; 025 import org.apache.commons.lang.StringUtils; 026 import org.sonar.api.ServerExtension; 027 028 import javax.annotation.CheckForNull; 029 import javax.annotation.Nullable; 030 import javax.annotation.concurrent.Immutable; 031 import java.util.Collection; 032 import java.util.List; 033 import java.util.Map; 034 035 /** 036 * Defines a web service implemented in Java (no Ruby on Rails at all). 037 * @since 4.2 038 */ 039 public interface WebService extends ServerExtension { 040 041 class Context { 042 private final Map<String, Controller> controllers = Maps.newHashMap(); 043 044 public NewController newController(String path) { 045 return new NewController(this, path); 046 } 047 048 private void register(NewController newController) { 049 if (controllers.containsKey(newController.path)) { 050 throw new IllegalStateException( 051 String.format("The web service '%s' is defined multiple times", newController.path) 052 ); 053 } 054 controllers.put(newController.path, new Controller(newController)); 055 } 056 057 @CheckForNull 058 public Controller controller(String key) { 059 return controllers.get(key); 060 } 061 062 public List<Controller> controllers() { 063 return ImmutableList.copyOf(controllers.values()); 064 } 065 } 066 067 class NewController { 068 private final Context context; 069 private final String path; 070 private String description, since; 071 private final Map<String, NewAction> actions = Maps.newHashMap(); 072 073 private NewController(Context context, String path) { 074 if (StringUtils.isBlank(path)) { 075 throw new IllegalArgumentException("Web service path can't be empty"); 076 } 077 this.context = context; 078 this.path = path; 079 } 080 081 public void done() { 082 context.register(this); 083 } 084 085 public NewController setDescription(@Nullable String s) { 086 this.description = s; 087 return this; 088 } 089 090 public NewController setSince(@Nullable String s) { 091 this.since = s; 092 return this; 093 } 094 095 public NewAction newAction(String actionKey) { 096 if (actions.containsKey(actionKey)) { 097 throw new IllegalStateException( 098 String.format("The action '%s' is defined multiple times in the web service '%s'", actionKey, path) 099 ); 100 } 101 NewAction action = new NewAction(actionKey); 102 actions.put(actionKey, action); 103 return action; 104 } 105 } 106 107 @Immutable 108 class Controller { 109 private final String path, description, since; 110 private final Map<String, Action> actions; 111 112 private Controller(NewController newController) { 113 if (newController.actions.isEmpty()) { 114 throw new IllegalStateException( 115 String.format("At least one action must be declared in the web service '%s'", newController.path) 116 ); 117 } 118 this.path = newController.path; 119 this.description = newController.description; 120 this.since = newController.since; 121 ImmutableMap.Builder<String, Action> mapBuilder = ImmutableMap.builder(); 122 for (NewAction newAction : newController.actions.values()) { 123 mapBuilder.put(newAction.key, new Action(this, newAction)); 124 } 125 this.actions = mapBuilder.build(); 126 } 127 128 public String path() { 129 return path; 130 } 131 132 @CheckForNull 133 public String description() { 134 return description; 135 } 136 137 @CheckForNull 138 public String since() { 139 return since; 140 } 141 142 @CheckForNull 143 public Action action(String actionKey) { 144 return actions.get(actionKey); 145 } 146 147 public Collection<Action> actions() { 148 return actions.values(); 149 } 150 } 151 152 class NewAction { 153 private final String key; 154 private String description, since; 155 private boolean post = false, isPrivate = false; 156 private RequestHandler handler; 157 private Map<String, NewParam> newParams = Maps.newHashMap(); 158 159 private NewAction(String key) { 160 this.key = key; 161 } 162 163 public NewAction setDescription(@Nullable String s) { 164 this.description = s; 165 return this; 166 } 167 168 public NewAction setSince(@Nullable String s) { 169 this.since = s; 170 return this; 171 } 172 173 public NewAction setPost(boolean b) { 174 this.post = b; 175 return this; 176 } 177 178 public NewAction setPrivate(boolean b) { 179 this.isPrivate = b; 180 return this; 181 } 182 183 public NewAction setHandler(RequestHandler h) { 184 this.handler = h; 185 return this; 186 } 187 188 public NewParam newParam(String paramKey) { 189 if (newParams.containsKey(paramKey)) { 190 throw new IllegalStateException( 191 String.format("The parameter '%s' is defined multiple times in the action '%s'", paramKey, key) 192 ); 193 } 194 NewParam newParam = new NewParam(paramKey); 195 newParams.put(paramKey, newParam); 196 return newParam; 197 } 198 199 public NewAction newParam(String paramKey, @Nullable String description) { 200 newParam(paramKey).setDescription(description); 201 return this; 202 } 203 } 204 205 @Immutable 206 class Action { 207 private final String key, path, description, since; 208 private final boolean post, isPrivate; 209 private final RequestHandler handler; 210 private final Map<String, Param> params; 211 212 private Action(Controller controller, NewAction newAction) { 213 this.key = newAction.key; 214 this.path = String.format("%s/%s", controller.path(), key); 215 this.description = newAction.description; 216 this.since = StringUtils.defaultIfBlank(newAction.since, controller.since); 217 this.post = newAction.post; 218 this.isPrivate = newAction.isPrivate; 219 220 if (newAction.handler == null) { 221 throw new IllegalStateException("RequestHandler is not set on action " + path); 222 } 223 this.handler = newAction.handler; 224 225 ImmutableMap.Builder<String, Param> mapBuilder = ImmutableMap.builder(); 226 for (NewParam newParam : newAction.newParams.values()) { 227 mapBuilder.put(newParam.key, new Param(newParam)); 228 } 229 this.params = mapBuilder.build(); 230 } 231 232 public String key() { 233 return key; 234 } 235 236 public String path() { 237 return path; 238 } 239 240 @CheckForNull 241 public String description() { 242 return description; 243 } 244 245 /** 246 * Set if different than controller. 247 */ 248 @CheckForNull 249 public String since() { 250 return since; 251 } 252 253 public boolean isPost() { 254 return post; 255 } 256 257 public boolean isPrivate() { 258 return isPrivate; 259 } 260 261 public RequestHandler handler() { 262 return handler; 263 } 264 265 @CheckForNull 266 public Param param(String key) { 267 return params.get(key); 268 } 269 270 public Collection<Param> params() { 271 return params.values(); 272 } 273 274 @Override 275 public String toString() { 276 return path; 277 } 278 } 279 280 class NewParam { 281 private String key, description; 282 283 private NewParam(String key) { 284 this.key = key; 285 } 286 287 public NewParam setDescription(@Nullable String s) { 288 this.description = s; 289 return this; 290 } 291 292 @Override 293 public String toString() { 294 return key; 295 } 296 } 297 298 @Immutable 299 class Param { 300 private final String key, description; 301 302 public Param(NewParam newParam) { 303 this.key = newParam.key; 304 this.description = newParam.description; 305 } 306 307 public String key() { 308 return key; 309 } 310 311 @CheckForNull 312 public String description() { 313 return description; 314 } 315 316 @Override 317 public String toString() { 318 return key; 319 } 320 } 321 322 /** 323 * Executed once at server startup. 324 */ 325 void define(Context context); 326 327 }