001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017
018import java.util.*;
019
020import org.apache.juneau.*;
021import org.apache.juneau.cp.*;
022import org.apache.juneau.http.response.*;
023import org.apache.juneau.internal.*;
024import org.apache.juneau.rest.annotation.*;
025
026/**
027 * Encapsulates the set of {@link RestOp}-annotated methods within a single {@link Rest}-annotated object.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 * </ul>
031 */
032public class RestOperations {
033
034   //-----------------------------------------------------------------------------------------------------------------
035   // Static
036   //-----------------------------------------------------------------------------------------------------------------
037
038   /**
039    * Represents a null value for the {@link Rest#restOperationsClass()} annotation.
040    */
041   @SuppressWarnings("javadoc")
042   public final class Void extends RestOperations {
043      public Void(Builder builder) throws Exception {
044         super(builder);
045      }
046   }
047
048   /**
049    * Static creator.
050    *
051    * @param beanStore The bean store to use for creating beans.
052    * @return A new builder for this object.
053    */
054   public static Builder create(BeanStore beanStore) {
055      return new Builder(beanStore);
056   }
057
058   //-----------------------------------------------------------------------------------------------------------------
059   // Builder
060   //-----------------------------------------------------------------------------------------------------------------
061
062   /**
063    * Builder class.
064    */
065   @FluentSetters
066   public static class Builder extends BeanBuilder<RestOperations> {
067
068      TreeMap<String,TreeSet<RestOpContext>> map;
069      Set<RestOpContext> set;
070
071      /**
072       * Constructor.
073       *
074       * @param beanStore The bean store to use for creating beans.
075       */
076      protected Builder(BeanStore beanStore) {
077         super(RestOperations.class, beanStore);
078         map = new TreeMap<>();
079         set = set();
080      }
081
082      @Override /* BeanBuilder */
083      protected RestOperations buildDefault() {
084         return new RestOperations(this);
085      }
086
087      //-------------------------------------------------------------------------------------------------------------
088      // Properties
089      //-------------------------------------------------------------------------------------------------------------
090
091      /**
092       * Adds a method context to this builder.
093       *
094       * @param value The REST method context to add.
095       * @return Adds a method context to this builder.
096       */
097      public Builder add(RestOpContext value) {
098         return add(value.getHttpMethod(), value);
099      }
100
101      /**
102       * Adds a method context to this builder.
103       *
104       * @param httpMethodName The HTTP method name.
105       * @param value The REST method context to add.
106       * @return Adds a method context to this builder.
107       */
108      public Builder add(String httpMethodName, RestOpContext value) {
109         httpMethodName = httpMethodName.toUpperCase();
110         if (! map.containsKey(httpMethodName))
111            map.put(httpMethodName, new TreeSet<>());
112         map.get(httpMethodName).add(value);
113         set.add(value);
114         return this;
115      }
116
117      // <FluentSetters>
118
119      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
120      public Builder impl(Object value) {
121         super.impl(value);
122         return this;
123      }
124
125      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
126      public Builder type(Class<?> value) {
127         super.type(value);
128         return this;
129      }
130
131      // </FluentSetters>
132   }
133
134   //-----------------------------------------------------------------------------------------------------------------
135   // Instance
136   //-----------------------------------------------------------------------------------------------------------------
137
138   private final Map<String,List<RestOpContext>> map;
139   private RestOpContext[] list;
140
141   /**
142    * Constructor.
143    *
144    * @param builder The builder containing the settings for this object.
145    */
146   public RestOperations(Builder builder) {
147      Map<String,List<RestOpContext>> m = map();
148      for (Map.Entry<String,TreeSet<RestOpContext>> e : builder.map.entrySet())
149         m.put(e.getKey(), listFrom(e.getValue()));
150      this.map = m;
151      this.list = array(builder.set, RestOpContext.class);
152   }
153
154   /**
155    * Finds the method that should handle the specified call.
156    *
157    * @param session The HTTP call.
158    * @return The method that should handle the specified call.
159    * @throws MethodNotAllowed If no methods implement the requested HTTP method.
160    * @throws PreconditionFailed At least one method was found but it didn't match one or more matchers.
161    * @throws NotFound HTTP method match was found but matching path was not.
162    */
163   public RestOpContext findOperation(RestSession session) throws MethodNotAllowed, PreconditionFailed, NotFound {
164      String m = session.getMethod();
165
166      int rc = 0;
167      if (map.containsKey(m)) {
168         for (RestOpContext oc : map.get(m)) {
169            int mrc = oc.match(session);
170            if (mrc == 2)
171               return oc;
172            rc = Math.max(rc, mrc);
173         }
174      }
175
176      if (map.containsKey("*")) {
177         for (RestOpContext oc : map.get("*")) {
178            int mrc = oc.match(session);
179            if (mrc == 2)
180               return oc;
181            rc = Math.max(rc, mrc);
182         }
183      }
184
185      // If no paths matched, see if the path matches any other methods.
186      // Note that we don't want to match against "/*" patterns such as getOptions().
187      if (rc == 0) {
188         for (RestOpContext oc : list) {
189            if (! oc.getPathPattern().endsWith("/*")) {
190               int orc = oc.match(session);
191               if (orc == 2)
192                  throw new MethodNotAllowed();
193            }
194         }
195      }
196
197      if (rc == 1)
198         throw new PreconditionFailed("Method ''{0}'' not found on resource on path ''{1}'' with matching matcher.", m, session.getPathInfo());
199
200      throw new NotFound("Java method matching path ''{0}'' not found on resource ''{1}''.", session.getPathInfo(), className(session.getResource()));
201   }
202
203
204   /**
205    * Returns the list of method contexts in this object.
206    *
207    * @return An unmodifiable list of method contexts in this object.
208    */
209   public List<RestOpContext> getOpContexts() {
210      return ulist(list);
211   }
212}