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