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}