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