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}