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.annotation; 014 015import static org.apache.juneau.BeanContext.*; 016import static org.apache.juneau.internal.ArrayUtils.*; 017import static org.apache.juneau.internal.ClassUtils.*; 018import static org.apache.juneau.internal.StringUtils.*; 019import static org.apache.juneau.rest.RestContext.*; 020import static org.apache.juneau.rest.RestMethodContext.*; 021import static org.apache.juneau.rest.util.RestUtils.*; 022import static org.apache.juneau.html.HtmlDocSerializer.*; 023import java.util.*; 024import java.util.logging.*; 025 026import org.apache.juneau.*; 027import org.apache.juneau.collections.*; 028import org.apache.juneau.internal.*; 029import org.apache.juneau.reflect.*; 030import org.apache.juneau.rest.*; 031import org.apache.juneau.rest.annotation.AnnotationUtils; 032import org.apache.juneau.rest.util.*; 033import org.apache.juneau.rest.widget.*; 034import org.apache.juneau.svl.*; 035 036/** 037 * Applies {@link RestMethod} annotations to a {@link PropertyStoreBuilder}. 038 */ 039public class RestMethodConfigApply extends ConfigApply<RestMethod> { 040 041 /** 042 * Constructor. 043 * 044 * @param c The annotation class. 045 * @param r The resolver for resolving values in annotations. 046 */ 047 public RestMethodConfigApply(Class<RestMethod> c, VarResolverSession r) { 048 super(c, r); 049 } 050 051 @SuppressWarnings("deprecation") 052 @Override 053 public void apply(AnnotationInfo<RestMethod> ai, PropertyStoreBuilder psb) { 054 RestMethod a = ai.getAnnotation(); 055 MethodInfo mi = ai.getMethodOn(); 056 String sig = mi == null ? "Unknown" : mi.getSignature(); 057 String s = null; 058 059 for (Property p1 : a.properties()) { 060 psb.set(p1.name(), string(p1.value())); // >>> DEPRECATED - Remove in 9.0 <<< 061 psb.putTo(REST_properties, string(p1.name()), string(p1.value())); 062 } 063 064 for (String p1 : a.flags()) { 065 psb.set(p1, true); // >>> DEPRECATED - Remove in 9.0 <<< 066 psb.putTo(REST_properties, string(p1), true); 067 } 068 069 if (a.serializers().length > 0) 070 psb.set(REST_serializers, merge(ConverterUtils.toType(psb.peek(REST_serializers), Object[].class), a.serializers())); 071 072 if (a.parsers().length > 0) 073 psb.set(REST_parsers, merge(ConverterUtils.toType(psb.peek(REST_parsers), Object[].class), a.parsers())); 074 075 if (a.encoders().length > 0) 076 psb.set(REST_encoders, merge(ConverterUtils.toType(psb.peek(REST_encoders), Object[].class), a.encoders())); 077 078 if (a.produces().length > 0) 079 psb.set(REST_produces, strings(a.produces())); 080 081 if (a.consumes().length > 0) 082 psb.set(REST_consumes, strings(a.consumes())); 083 084 for (String header : strings(a.defaultRequestHeaders())) { 085 String[] h = RestUtils.parseHeader(header); 086 if (h == null) 087 throw new ConfigException("Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''Header-Name: header-value''", sig, header); 088 if (isNotEmpty(h[1])) 089 psb.putTo(REST_defaultRequestHeaders, h[0], h[1]); 090 } 091 092 for (String header : strings(a.reqHeaders())) { 093 String[] h = RestUtils.parseHeader(header); 094 if (h == null) 095 throw new ConfigException("Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''Header-Name: header-value''", sig, header); 096 if (isNotEmpty(h[1])) 097 psb.putTo(REST_reqHeaders, h[0], h[1]); 098 } 099 100 if (a.defaultAccept().length() > 0) { 101 s = string(a.defaultAccept()); 102 if (isNotEmpty(s)) 103 psb.putTo(REST_reqHeaders, "Accept", s); 104 } 105 106 if (a.defaultContentType().length() > 0) { 107 s = string(a.defaultContentType()); 108 if (isNotEmpty(s)) 109 psb.putTo(REST_reqHeaders, "Content-Type", s); 110 } 111 112 psb.prependTo(REST_converters, a.converters()); 113 114 psb.prependTo(REST_guards, reverse(a.guards())); 115 116 psb.prependTo(RESTMETHOD_matchers, a.matchers()); 117 118 if (! a.clientVersion().isEmpty()) 119 psb.set(RESTMETHOD_clientVersion, a.clientVersion()); 120 121 psb.set(BEAN_beanFilters, merge(ConverterUtils.toType(psb.peek(BEAN_beanFilters), Object[].class), a.beanFilters())); 122 123 psb.set(BEAN_pojoSwaps, merge(ConverterUtils.toType(psb.peek(BEAN_pojoSwaps), Object[].class), a.pojoSwaps())); 124 125 if (a.bpi().length > 0) { 126 Map<String,String> bpiMap = new LinkedHashMap<>(); 127 for (String s1 : a.bpi()) { 128 for (String s2 : split(s1, ';')) { 129 int i = s2.indexOf(':'); 130 if (i == -1) 131 throw new ConfigException( 132 "Invalid format for @RestMethod(bpi) on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s1); 133 bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim()); 134 } 135 } 136 psb.putAllTo(BEAN_bpi, bpiMap); 137 } 138 139 if (a.bpx().length > 0) { 140 Map<String,String> bpxMap = new LinkedHashMap<>(); 141 for (String s1 : a.bpx()) { 142 for (String s2 : split(s1, ';')) { 143 int i = s2.indexOf(':'); 144 if (i == -1) 145 throw new ConfigException( 146 "Invalid format for @RestMethod(bpx) on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s1); 147 bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim()); 148 } 149 } 150 psb.putAllTo(BEAN_bpx, bpxMap); 151 } 152 153 if (! a.defaultCharset().isEmpty()) 154 psb.set(REST_defaultCharset, string(a.defaultCharset())); 155 156 if (! a.maxInput().isEmpty()) 157 psb.set(REST_maxInput, string(a.maxInput())); 158 159 if (! a.maxInput().isEmpty()) 160 psb.set(REST_maxInput, string(a.maxInput())); 161 162 if (! a.path().isEmpty()) 163 psb.prependTo(RESTMETHOD_paths, string(a.path())); 164 for (String p : a.paths()) 165 psb.prependTo(RESTMETHOD_paths, string(p)); 166 167 if (! a.rolesDeclared().isEmpty()) 168 psb.addTo(REST_rolesDeclared, strings(a.rolesDeclared())); 169 170 if (! a.roleGuard().isEmpty()) 171 psb.addTo(REST_roleGuard, string(a.roleGuard())); 172 173 for (String h : a.defaultRequestHeaders()) { 174 String[] h2 = RestUtils.parseKeyValuePair(string(h)); 175 if (h2 == null) 176 throw new ConfigException( 177 "Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 178 psb.putTo(RESTMETHOD_defaultRequestHeaders, h2[0], h2[1]); 179 } 180 181 for (String h : a.reqHeaders()) { 182 String[] h2 = RestUtils.parseKeyValuePair(string(h)); 183 if (h2 == null) 184 throw new ConfigException( 185 "Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 186 psb.putTo(RESTMETHOD_reqHeaders, h2[0], h2[1]); 187 } 188 189 for (String ra : a.attrs()) { 190 String[] ra2 = RestUtils.parseKeyValuePair(string(ra)); 191 if (ra2 == null) 192 throw new ConfigException( 193 "Invalid default request attribute specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 194 psb.putTo(RESTMETHOD_attrs, ra2[0], ra2[1]); 195 } 196 197 for (String ra : a.reqAttrs()) { 198 String[] ra2 = RestUtils.parseKeyValuePair(string(ra)); 199 if (ra2 == null) 200 throw new ConfigException( 201 "Invalid default request attribute specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 202 psb.putTo(RESTMETHOD_reqAttrs, ra2[0], ra2[1]); 203 } 204 205 if (! a.defaultAccept().isEmpty()) 206 psb.putTo(RESTMETHOD_reqHeaders, "Accept", string(a.defaultAccept())); 207 208 if (! a.defaultContentType().isEmpty()) 209 psb.putAllTo(RESTMETHOD_reqHeaders, string(a.defaultContentType())); 210 211 for (String h : a.defaultQuery()) { 212 String[] h2 = RestUtils.parseKeyValuePair(string(h)); 213 if (h == null) 214 throw new ConfigException( 215 "Invalid default query parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 216 psb.putTo(RESTMETHOD_defaultQuery, h2[0], h2[1]); 217 } 218 219 for (String h : a.defaultFormData()) { 220 String[] h2 = RestUtils.parseKeyValuePair(string(h)); 221 if (h == null) 222 throw new ConfigException( 223 "Invalid default form data parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 224 psb.putTo(RESTMETHOD_defaultFormData, h2[0], h2[1]); 225 } 226 227 if (! a.method().isEmpty()) 228 psb.set(RESTMETHOD_httpMethod, string(a.method())); 229 230 if (! a.name().isEmpty()) 231 psb.set(RESTMETHOD_httpMethod, string(a.name())); 232 233 if (a.priority() != 0) 234 psb.set(RESTMETHOD_priority, a.priority()); 235 236 if (! a.debug().isEmpty()) 237 psb.set(RESTMETHOD_debug, string(a.debug())); 238 239 if (! AnnotationUtils.empty(a.logging())) { 240 Logging al = a.logging(); 241 OMap m = new OMap(psb.peek(OMap.class, RESTMETHOD_callLoggerConfig)); 242 243 if (! al.useStackTraceHashing().isEmpty()) 244 m.append("useStackTraceHashing", bool(al.useStackTraceHashing())); 245 246 if (! al.stackTraceHashingTimeout().isEmpty()) 247 m.append("stackTraceHashingTimeout", integer(al.stackTraceHashingTimeout(), "@Logging(stackTraceHashingTimeout)")); 248 249 if (! al.disabled().isEmpty()) 250 m.append("disabled", enablement(al.disabled())); 251 252 if (! al.level().isEmpty()) 253 m.append("level", level(al.level(), "@Logging(level)")); 254 255 if (al.rules().length > 0) { 256 OList ol = new OList(); 257 for (LoggingRule a2 : al.rules()) { 258 OMap m2 = new OMap(); 259 260 if (! a2.codes().isEmpty()) 261 m2.append("codes", string(a2.codes())); 262 263 if (! a2.exceptions().isEmpty()) 264 m2.append("exceptions", string(a2.exceptions())); 265 266 if (! a2.debugOnly().isEmpty()) 267 m2.append("debugOnly", bool(a2.debugOnly())); 268 269 if (! a2.level().isEmpty()) 270 m2.append("level", level(a2.level(), "@LoggingRule(level)")); 271 272 if (! a2.req().isEmpty()) 273 m2.append("req", string(a2.req())); 274 275 if (! a2.res().isEmpty()) 276 m2.append("res", string(a2.res())); 277 278 if (! a2.verbose().isEmpty()) 279 m2.append("verbose", bool(a2.verbose())); 280 281 if (! a2.disabled().isEmpty()) 282 m2.append("disabled", bool(a2.disabled())); 283 284 ol.add(m2); 285 } 286 m.put("rules", ol.appendAll(m.getList("rules"))); 287 } 288 289 psb.set(RESTMETHOD_callLoggerConfig, m); 290 } 291 292 HtmlDoc hd = a.htmldoc(); 293 new HtmlDocBuilder(psb).process(hd); 294 for (Class<? extends Widget> wc : hd.widgets()) { 295 Widget w = castOrCreate(Widget.class, wc); 296 psb.prependTo(REST_widgets, w); 297 psb.prependTo(HTMLDOC_script, "$W{"+w.getName()+".script}"); 298 psb.prependTo(HTMLDOC_script, "$W{"+w.getName()+".style}"); 299 } 300 } 301 302 private Enablement enablement(String in) { 303 return Enablement.fromString(string(in)); 304 } 305 306 private Level level(String in, String loc) { 307 try { 308 return Level.parse(string(in)); 309 } catch (Exception e) { 310 throw new ConfigException("Invalid syntax for level on annotation @RestMethod({1}): {2}", loc, in); 311 } 312 } 313}