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}