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 */
020package org.sonar.api.server.ws;
021
022import com.google.common.collect.ImmutableList;
023import com.google.common.collect.ImmutableMap;
024import com.google.common.collect.Maps;
025import org.apache.commons.lang.StringUtils;
026import org.sonar.api.ServerExtension;
027
028import javax.annotation.CheckForNull;
029import javax.annotation.Nullable;
030import javax.annotation.concurrent.Immutable;
031import java.util.Collection;
032import java.util.List;
033import java.util.Map;
034
035/**
036 * Defines a web service implemented in Java (no Ruby on Rails at all).
037 * @since 4.2
038 */
039public 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, isInternal = 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 setInternal(boolean b) {
179      this.isInternal = 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, isInternal;
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.isInternal = newAction.isInternal;
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 isInternal() {
258      return isInternal;
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}