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}