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}