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.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.util.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.cp.*;
026import org.apache.juneau.http.response.*;
027import org.apache.juneau.rest.annotation.*;
028
029/**
030 * Encapsulates the set of {@link RestOp}-annotated methods within a single {@link Rest}-annotated object.
031 *
032 */
033public class RestOperations {
034   /**
035    * Builder class.
036    */
037   public static class Builder extends BeanBuilder<RestOperations> {
038
039      TreeMap<String,TreeSet<RestOpContext>> map;
040      Set<RestOpContext> set;
041
042      /**
043       * Constructor.
044       *
045       * @param beanStore The bean store to use for creating beans.
046       */
047      protected Builder(BeanStore beanStore) {
048         super(RestOperations.class, beanStore);
049         map = new TreeMap<>();
050         set = set();
051      }
052
053      /**
054       * Adds a method context to this builder.
055       *
056       * @param value The REST method context to add.
057       * @return Adds a method context to this builder.
058       */
059      public Builder add(RestOpContext value) {
060         return add(value.getHttpMethod(), value);
061      }
062
063      /**
064       * Adds a method context to this builder.
065       *
066       * @param httpMethodName The HTTP method name.
067       * @param value The REST method context to add.
068       * @return Adds a method context to this builder.
069       */
070      public Builder add(String httpMethodName, RestOpContext value) {
071         httpMethodName = httpMethodName.toUpperCase();
072         if (! map.containsKey(httpMethodName))
073            map.put(httpMethodName, new TreeSet<>());
074         map.get(httpMethodName).add(value);
075         set.add(value);
076         return this;
077      }
078
079      @Override /* Overridden from BeanBuilder */
080      public Builder impl(Object value) {
081         super.impl(value);
082         return this;
083      }
084
085      @Override /* Overridden from BeanBuilder */
086      public Builder type(Class<?> value) {
087         super.type(value);
088         return this;
089      }
090
091      @Override /* Overridden from BeanBuilder */
092      protected RestOperations buildDefault() {
093         return new RestOperations(this);
094      }
095   }
096
097   /**
098    * Represents a null value for the RestOperations class.
099    * 
100    * <p>
101    * This is used internally when no custom RestOperations implementation is specified.
102    * The {@code Void} class is used as a placeholder when the {@link Rest} annotation
103    * does not specify a custom {@code RestOperations} class.
104    */
105   @SuppressWarnings("javadoc")
106   public final class Void extends RestOperations {
107      public Void(Builder builder) throws Exception {
108         super(builder);
109      }
110   }
111
112   /**
113    * Static creator.
114    *
115    * @param beanStore The bean store to use for creating beans.
116    * @return A new builder for this object.
117    */
118   public static Builder create(BeanStore beanStore) {
119      return new Builder(beanStore);
120   }
121
122   private final Map<String,List<RestOpContext>> map;
123   private RestOpContext[] list;
124
125   /**
126    * Constructor.
127    *
128    * @param builder The builder containing the settings for this object.
129    */
130   public RestOperations(Builder builder) {
131      Map<String,List<RestOpContext>> m = map();
132      for (var e : builder.map.entrySet())
133         m.put(e.getKey(), toList(e.getValue()));
134      this.map = m;
135      this.list = array(builder.set, RestOpContext.class);
136   }
137
138   /**
139    * Finds the method that should handle the specified call.
140    *
141    * @param session The HTTP call.
142    * @return The method that should handle the specified call.
143    * @throws MethodNotAllowed If no methods implement the requested HTTP method.
144    * @throws PreconditionFailed At least one method was found but it didn't match one or more matchers.
145    * @throws NotFound HTTP method match was found but matching path was not.
146    */
147   public RestOpContext findOperation(RestSession session) throws MethodNotAllowed, PreconditionFailed, NotFound {
148      String m = session.getMethod();
149
150      int rc = 0;
151      if (map.containsKey(m)) {
152         for (var oc : map.get(m)) {
153            int mrc = oc.match(session);
154            if (mrc == 2)
155               return oc;
156            rc = Math.max(rc, mrc);
157         }
158      }
159
160      if (map.containsKey("*")) {
161         for (var oc : map.get("*")) {
162            int mrc = oc.match(session);
163            if (mrc == 2)
164               return oc;
165            rc = Math.max(rc, mrc);
166         }
167      }
168
169      // If no paths matched, see if the path matches any other methods.
170      // Note that we don't want to match against "/*" patterns such as getOptions().
171      if (rc == 0) {
172         for (var oc : list) {
173            if (! oc.getPathPattern().endsWith("/*")) {
174               int orc = oc.match(session);
175               if (orc == 2)
176                  throw new MethodNotAllowed();
177            }
178         }
179      }
180
181      if (rc == 1)
182         throw new PreconditionFailed("Method ''{0}'' not found on resource on path ''{1}'' with matching matcher.", m, session.getPathInfo());
183
184      throw new NotFound("Java method matching path ''{0}'' not found on resource ''{1}''.", session.getPathInfo(), cn(session.getResource()));
185   }
186
187   /**
188    * Returns the list of method contexts in this object.
189    *
190    * @return An unmodifiable list of method contexts in this object.
191    */
192   public List<RestOpContext> getOpContexts() { return u(l(list)); }
193}