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    }