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; 014 015import static javax.servlet.http.HttpServletResponse.*; 016import static org.apache.juneau.BeanContext.*; 017import static org.apache.juneau.internal.ClassUtils.*; 018import static org.apache.juneau.internal.CollectionUtils.*; 019import static org.apache.juneau.internal.StringUtils.*; 020import static org.apache.juneau.internal.Utils.*; 021import static org.apache.juneau.rest.RestContext.*; 022import static org.apache.juneau.rest.util.RestUtils.*; 023import static org.apache.juneau.httppart.HttpPartType.*; 024 025import java.lang.annotation.*; 026import java.lang.reflect.*; 027import java.util.*; 028import java.util.concurrent.*; 029 030import javax.servlet.http.*; 031 032import org.apache.juneau.*; 033import org.apache.juneau.encoders.*; 034import org.apache.juneau.http.*; 035import org.apache.juneau.http.annotation.*; 036import org.apache.juneau.httppart.*; 037import org.apache.juneau.httppart.bean.*; 038import org.apache.juneau.internal.*; 039import org.apache.juneau.internal.HttpUtils; 040import org.apache.juneau.parser.*; 041import org.apache.juneau.rest.annotation.*; 042import org.apache.juneau.rest.exception.*; 043import org.apache.juneau.rest.util.*; 044import org.apache.juneau.rest.widget.*; 045import org.apache.juneau.serializer.*; 046import org.apache.juneau.svl.*; 047 048/** 049 * Represents a single Java servlet/resource method annotated with {@link RestMethod @RestMethod}. 050 */ 051public class RestJavaMethod implements Comparable<RestJavaMethod> { 052 private final String httpMethod; 053 private final UrlPathPattern pathPattern; 054 final RestMethodParam[] methodParams; 055 private final RestGuard[] guards; 056 private final RestMatcher[] optionalMatchers; 057 private final RestMatcher[] requiredMatchers; 058 private final RestConverter[] converters; 059 private final RestMethodProperties properties; 060 private final Integer priority; 061 private final RestContext context; 062 final java.lang.reflect.Method method; 063 final PropertyStore propertyStore; 064 final SerializerGroup serializers; 065 final ParserGroup parsers; 066 final EncoderGroup encoders; 067 final HttpPartSerializer partSerializer; 068 final HttpPartParser partParser; 069 final Map<String,Object> 070 defaultRequestHeaders, 071 defaultQuery, 072 defaultFormData; 073 final String defaultCharset; 074 final long maxInput; 075 final BeanContext beanContext; 076 final Map<String,Widget> widgets; 077 final List<MediaType> 078 supportedAcceptTypes, 079 supportedContentTypes; 080 081 final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new ConcurrentHashMap<>(); 082 final Map<Class<?>,ResponsePartMeta> headerPartMetas = new ConcurrentHashMap<>(); 083 final Map<Class<?>,ResponsePartMeta> bodyPartMetas = new ConcurrentHashMap<>(); 084 final ResponseBeanMeta responseMeta; 085 086 RestJavaMethod(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException { 087 Builder b = new Builder(servlet, method, context); 088 this.context = context; 089 this.method = method; 090 this.httpMethod = b.httpMethod; 091 this.pathPattern = b.pathPattern; 092 this.methodParams = b.methodParams; 093 this.guards = b.guards; 094 this.optionalMatchers = b.optionalMatchers; 095 this.requiredMatchers = b.requiredMatchers; 096 this.converters = b.converters; 097 this.serializers = b.serializers; 098 this.parsers = b.parsers; 099 this.encoders = b.encoders; 100 this.partParser = b.partParser; 101 this.partSerializer = b.partSerializer; 102 this.beanContext = b.beanContext; 103 this.properties = b.properties; 104 this.propertyStore = b.propertyStore; 105 this.defaultRequestHeaders = b.defaultRequestHeaders; 106 this.defaultQuery = b.defaultQuery; 107 this.defaultFormData = b.defaultFormData; 108 this.defaultCharset = b.defaultCharset; 109 this.maxInput = b.maxInput; 110 this.priority = b.priority; 111 this.supportedAcceptTypes = b.supportedAcceptTypes; 112 this.supportedContentTypes = b.supportedContentTypes; 113 this.responseMeta = b.responseMeta; 114 this.widgets = unmodifiableMap(b.widgets); 115 } 116 117 private static final class Builder { 118 String httpMethod, defaultCharset; 119 UrlPathPattern pathPattern; 120 RestMethodParam[] methodParams; 121 RestGuard[] guards; 122 RestMatcher[] optionalMatchers, requiredMatchers; 123 RestConverter[] converters; 124 SerializerGroup serializers; 125 ParserGroup parsers; 126 EncoderGroup encoders; 127 HttpPartParser partParser; 128 HttpPartSerializer partSerializer; 129 BeanContext beanContext; 130 RestMethodProperties properties; 131 PropertyStore propertyStore; 132 Map<String,Object> defaultRequestHeaders, defaultQuery, defaultFormData; 133 long maxInput; 134 Integer priority; 135 Map<String,Widget> widgets; 136 List<MediaType> supportedAcceptTypes, supportedContentTypes; 137 ResponseBeanMeta responseMeta; 138 139 Builder(Object servlet, java.lang.reflect.Method method, RestContext context) throws RestServletException { 140 String sig = method.getDeclaringClass().getName() + '.' + method.getName(); 141 142 try { 143 144 RestMethod m = method.getAnnotation(RestMethod.class); 145 if (m == null) 146 throw new RestServletException("@RestMethod annotation not found on method ''{0}''", sig); 147 148 VarResolver vr = context.getVarResolver(); 149 150 serializers = context.getSerializers(); 151 parsers = context.getParsers(); 152 partSerializer = context.getPartSerializer(); 153 partParser = context.getPartParser(); 154 beanContext = context.getBeanContext(); 155 encoders = context.getEncoders(); 156 properties = new RestMethodProperties(context.getProperties()); 157 defaultCharset = context.getDefaultCharset(); 158 maxInput = context.getMaxInput(); 159 160 if (! m.defaultCharset().isEmpty()) 161 defaultCharset = vr.resolve(m.defaultCharset()); 162 if (! m.maxInput().isEmpty()) 163 maxInput = StringUtils.parseLongWithSuffix(vr.resolve(m.maxInput())); 164 165 HtmlDocBuilder hdb = new HtmlDocBuilder(properties); 166 167 HtmlDoc hd = m.htmldoc(); 168 hdb.process(hd); 169 170 widgets = new HashMap<>(context.getWidgets()); 171 for (Class<? extends Widget> wc : hd.widgets()) { 172 Widget w = beanContext.newInstance(Widget.class, wc); 173 widgets.put(w.getName(), w); 174 hdb.script("INHERIT", "$W{"+w.getName()+".script}"); 175 hdb.style("INHERIT", "$W{"+w.getName()+".style}"); 176 } 177 178 SerializerGroupBuilder sgb = null; 179 ParserGroupBuilder pgb = null; 180 ParserBuilder uepb = null; 181 BeanContextBuilder bcb = null; 182 PropertyStore cps = context.getPropertyStore(); 183 184 Object[] mSerializers = merge(cps.getArrayProperty(REST_serializers, Object.class), m.serializers()); 185 Object[] mParsers = merge(cps.getArrayProperty(REST_parsers, Object.class), m.parsers()); 186 Object[] mPojoSwaps = merge(cps.getArrayProperty(BEAN_pojoSwaps, Object.class), m.pojoSwaps()); 187 Object[] mBeanFilters = merge(cps.getArrayProperty(BEAN_beanFilters, Object.class), m.beanFilters()); 188 189 if (m.serializers().length > 0 || m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0 190 || m.beanFilters().length > 0 || m.pojoSwaps().length > 0 || m.bpi().length > 0 191 || m.bpx().length > 0) { 192 sgb = SerializerGroup.create(); 193 pgb = ParserGroup.create(); 194 uepb = Parser.create(); 195 bcb = beanContext.builder(); 196 sgb.append(mSerializers); 197 pgb.append(mParsers); 198 } 199 200 String p = m.path(); 201 if (isEmpty(p)) 202 p = HttpUtils.detectHttpPath(method, true); 203 204 httpMethod = m.name().toUpperCase(Locale.ENGLISH); 205 if (httpMethod.isEmpty()) 206 httpMethod = HttpUtils.detectHttpMethod(method, true, "GET"); 207 if ("METHOD".equals(httpMethod)) 208 httpMethod = "*"; 209 210 priority = m.priority(); 211 212 converters = new RestConverter[m.converters().length]; 213 for (int i = 0; i < converters.length; i++) 214 converters[i] = beanContext.newInstance(RestConverter.class, m.converters()[i]); 215 216 guards = new RestGuard[m.guards().length]; 217 for (int i = 0; i < guards.length; i++) 218 guards[i] = beanContext.newInstance(RestGuard.class, m.guards()[i]); 219 220 List<RestMatcher> optionalMatchers = new LinkedList<>(), requiredMatchers = new LinkedList<>(); 221 for (int i = 0; i < m.matchers().length; i++) { 222 Class<? extends RestMatcher> c = m.matchers()[i]; 223 RestMatcher matcher = beanContext.newInstance(RestMatcher.class, c, true, servlet, method); 224 if (matcher.mustMatch()) 225 requiredMatchers.add(matcher); 226 else 227 optionalMatchers.add(matcher); 228 } 229 if (! m.clientVersion().isEmpty()) 230 requiredMatchers.add(new ClientVersionMatcher(context.getClientVersionHeader(), method)); 231 232 this.requiredMatchers = requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]); 233 this.optionalMatchers = optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]); 234 235 PropertyStoreBuilder psb = context.getPropertyStore().builder().set(BEAN_beanFilters, mBeanFilters).set(BEAN_pojoSwaps, mPojoSwaps); 236 for (Property p1 : m.properties()) 237 psb.set(p1.name(), p1.value()); 238 for (String p1 : m.flags()) 239 psb.set(p1, true); 240 this.propertyStore = psb.build(); 241 242 if (sgb != null) { 243 sgb.apply(propertyStore); 244 if (m.bpi().length > 0) { 245 Map<String,String> bpiMap = new LinkedHashMap<>(); 246 for (String s : m.bpi()) { 247 for (String s2 : split(s, ';')) { 248 int i = s2.indexOf(':'); 249 if (i == -1) 250 throw new RestServletException( 251 "Invalid format for @RestMethod(bpi) on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s); 252 bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim()); 253 } 254 } 255 sgb.includeProperties(bpiMap); 256 } 257 if (m.bpx().length > 0) { 258 Map<String,String> bpxMap = new LinkedHashMap<>(); 259 for (String s : m.bpx()) { 260 for (String s2 : split(s, ';')) { 261 int i = s2.indexOf(':'); 262 if (i == -1) 263 throw new RestServletException( 264 "Invalid format for @RestMethod(bpx) on method ''{0}''. Must be in the format \"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s); 265 bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim()); 266 } 267 } 268 sgb.excludeProperties(bpxMap); 269 } 270 sgb.beanFilters(mBeanFilters); 271 sgb.pojoSwaps(mPojoSwaps); 272 } 273 274 if (pgb != null) { 275 pgb.apply(propertyStore); 276 pgb.beanFilters(mBeanFilters); 277 pgb.pojoSwaps(mPojoSwaps); 278 } 279 280 if (uepb != null) { 281 uepb.apply(propertyStore); 282 uepb.beanFilters(mBeanFilters); 283 uepb.pojoSwaps(mPojoSwaps); 284 } 285 286 if (bcb != null) { 287 bcb.apply(propertyStore); 288 bcb.pojoSwaps(mPojoSwaps); 289 } 290 291 if (m.properties().length > 0 || m.flags().length > 0) { 292 properties = new RestMethodProperties(properties); 293 for (Property p1 : m.properties()) 294 properties.put(p1.name(), p1.value()); 295 for (String p1 : m.flags()) 296 properties.put(p1, true); 297 } 298 299 if (m.encoders().length > 0) { 300 EncoderGroupBuilder g = EncoderGroup.create().append(IdentityEncoder.INSTANCE); 301 for (Class<?> c : m.encoders()) { 302 try { 303 g.append(c); 304 } catch (Exception e) { 305 throw new RestServletException( 306 "Exception occurred while trying to instantiate ConfigEncoder on method ''{0}'': ''{1}''", sig, c.getSimpleName()).initCause(e); 307 } 308 } 309 encoders = g.build(); 310 } 311 312 defaultRequestHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 313 for (String s : m.defaultRequestHeaders()) { 314 String[] h = RestUtils.parseKeyValuePair(vr.resolve(s)); 315 if (h == null) 316 throw new RestServletException( 317 "Invalid default request header specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 318 defaultRequestHeaders.put(h[0], h[1]); 319 } 320 321 String defaultAccept = vr.resolve(m.defaultAccept()); 322 if (isNotEmpty(defaultAccept)) 323 defaultRequestHeaders.put("Accept", defaultAccept); 324 325 String defaultContentType = vr.resolve(m.defaultContentType()); 326 if (isNotEmpty(defaultContentType)) 327 defaultRequestHeaders.put("Content-Type", defaultAccept); 328 329 defaultQuery = new LinkedHashMap<>(); 330 for (String s : m.defaultQuery()) { 331 String[] h = RestUtils.parseKeyValuePair(vr.resolve(s)); 332 if (h == null) 333 throw new RestServletException( 334 "Invalid default query parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 335 defaultQuery.put(h[0], h[1]); 336 } 337 338 defaultFormData = new LinkedHashMap<>(); 339 for (String s : m.defaultFormData()) { 340 String[] h = RestUtils.parseKeyValuePair(vr.resolve(s)); 341 if (h == null) 342 throw new RestServletException( 343 "Invalid default form data parameter specified on method ''{0}'': ''{1}''. Must be in the format: ''name[:=]value''", sig, s); 344 defaultFormData.put(h[0], h[1]); 345 } 346 347 Type[] pt = method.getGenericParameterTypes(); 348 Annotation[][] pa = method.getParameterAnnotations(); 349 for (int i = 0; i < pt.length; i++) { 350 for (Annotation a : pa[i]) { 351 if (a instanceof Header) { 352 Header h = (Header)a; 353 if (h._default().length > 0) 354 defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), parseAnything(joinnl(h._default()))); 355 } else if (a instanceof Query) { 356 Query q = (Query)a; 357 if (q._default().length > 0) 358 defaultQuery.put(firstNonEmpty(q.name(), q.value()), parseAnything(joinnl(q._default()))); 359 } else if (a instanceof FormData) { 360 FormData f = (FormData)a; 361 if (f._default().length > 0) 362 defaultFormData.put(firstNonEmpty(f.name(), f.value()), parseAnything(joinnl(f._default()))); 363 } 364 } 365 } 366 367 pathPattern = new UrlPathPattern(p); 368 369 if (sgb != null) 370 serializers = sgb.build(); 371 if (pgb != null) 372 parsers = pgb.build(); 373 if (uepb != null && partParser instanceof Parser) { 374 Parser pp = (Parser)partParser; 375 partParser = (HttpPartParser)pp.builder().apply(uepb.getPropertyStore()).build(); 376 } 377 if (bcb != null) 378 beanContext = bcb.build(); 379 380 supportedAcceptTypes = 381 m.produces().length > 0 382 ? immutableList(MediaType.forStrings(resolveVars(vr, m.produces()))) 383 : serializers.getSupportedMediaTypes(); 384 supportedContentTypes = 385 m.consumes().length > 0 386 ? immutableList(MediaType.forStrings(resolveVars(vr, m.consumes()))) 387 : parsers.getSupportedMediaTypes(); 388 389 methodParams = context.findParams(method, pathPattern, false); 390 391 if (hasAnnotation(Response.class, method)) 392 responseMeta = ResponseBeanMeta.create(method, serializers.getPropertyStore()); 393 394 // Need this to access methods in anonymous inner classes. 395 setAccessible(method, true); 396 } catch (RestServletException e) { 397 throw e; 398 } catch (Exception e) { 399 throw new RestServletException("Exception occurred while initializing method ''{0}''", sig).initCause(e); 400 } 401 } 402 } 403 404 ResponseBeanMeta getResponseBeanMeta(Object o) { 405 if (o == null) 406 return null; 407 Class<?> c = o.getClass(); 408 ResponseBeanMeta rbm = responseBeanMetas.get(c); 409 if (rbm == null) { 410 rbm = ResponseBeanMeta.create(c, serializers.getPropertyStore()); 411 if (rbm == null) 412 rbm = ResponseBeanMeta.NULL; 413 responseBeanMetas.put(c, rbm); 414 } 415 if (rbm == ResponseBeanMeta.NULL) 416 return null; 417 return rbm; 418 } 419 420 ResponsePartMeta getResponseHeaderMeta(Object o) { 421 if (o == null) 422 return null; 423 Class<?> c = o.getClass(); 424 ResponsePartMeta pm = headerPartMetas.get(c); 425 if (pm == null) { 426 ResponseHeader a = c.getAnnotation(ResponseHeader.class); 427 if (a != null) { 428 HttpPartSchema schema = HttpPartSchema.create(a); 429 HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), serializers.getPropertyStore(), partSerializer); 430 pm = new ResponsePartMeta(HEADER, schema, serializer); 431 } 432 if (pm == null) 433 pm = ResponsePartMeta.NULL; 434 headerPartMetas.put(c, pm); 435 } 436 if (pm == ResponsePartMeta.NULL) 437 return null; 438 return pm; 439 } 440 441 ResponsePartMeta getResponseBodyMeta(Object o) { 442 if (o == null) 443 return null; 444 Class<?> c = o.getClass(); 445 ResponsePartMeta pm = bodyPartMetas.get(c); 446 if (pm == null) { 447 ResponseBody a = c.getAnnotation(ResponseBody.class); 448 if (a != null) { 449 HttpPartSchema schema = HttpPartSchema.create(a); 450 HttpPartSerializer serializer = createPartSerializer(schema.getSerializer(), serializers.getPropertyStore(), partSerializer); 451 pm = new ResponsePartMeta(BODY, schema, serializer); 452 } 453 if (pm == null) 454 pm = ResponsePartMeta.NULL; 455 bodyPartMetas.put(c, pm); 456 } 457 if (pm == ResponsePartMeta.NULL) 458 return null; 459 return pm; 460 } 461 462 /** 463 * Returns <jk>true</jk> if this Java method has any guards or matchers. 464 */ 465 boolean hasGuardsOrMatchers() { 466 return (guards.length != 0 || requiredMatchers.length != 0 || optionalMatchers.length != 0); 467 } 468 469 /** 470 * Returns the HTTP method name (e.g. <js>"GET"</js>). 471 */ 472 String getHttpMethod() { 473 return httpMethod; 474 } 475 476 /** 477 * Returns the path pattern for this method. 478 */ 479 String getPathPattern() { 480 return pathPattern.toString(); 481 } 482 483 /** 484 * Returns <jk>true</jk> if the specified request object can call this method. 485 */ 486 boolean isRequestAllowed(RestRequest req) { 487 for (RestGuard guard : guards) { 488 req.setJavaMethod(method); 489 if (! guard.isRequestAllowed(req)) 490 return false; 491 } 492 return true; 493 } 494 495 /** 496 * Workhorse method. 497 * 498 * @param pathInfo The value of {@link HttpServletRequest#getPathInfo()} (sorta) 499 * @return The HTTP response code. 500 */ 501 int invoke(String pathInfo, RestRequest req, RestResponse res) throws Throwable { 502 503 String[] patternVals = pathPattern.match(pathInfo); 504 if (patternVals == null) 505 return SC_NOT_FOUND; 506 507 String remainder = null; 508 if (patternVals.length > pathPattern.getVars().length) 509 remainder = patternVals[pathPattern.getVars().length]; 510 for (int i = 0; i < pathPattern.getVars().length; i++) 511 req.getPathMatch().put(pathPattern.getVars()[i], patternVals[i]); 512 req.getPathMatch().pattern(pathPattern.getPatternString()).remainder(remainder); 513 514 RequestProperties requestProperties = new RequestProperties(req.getVarResolverSession(), properties); 515 516 req.init(this, requestProperties); 517 res.init(this, requestProperties); 518 519 // Class-level guards 520 for (RestGuard guard : context.getGuards()) 521 if (! guard.guard(req, res)) 522 return SC_UNAUTHORIZED; 523 524 // If the method implements matchers, test them. 525 for (RestMatcher m : requiredMatchers) 526 if (! m.matches(req)) 527 return SC_PRECONDITION_FAILED; 528 if (optionalMatchers.length > 0) { 529 boolean matches = false; 530 for (RestMatcher m : optionalMatchers) 531 matches |= m.matches(req); 532 if (! matches) 533 return SC_PRECONDITION_FAILED; 534 } 535 536 context.preCall(req, res); 537 538 Object[] args = new Object[methodParams.length]; 539 for (int i = 0; i < methodParams.length; i++) { 540 try { 541 args[i] = methodParams[i].resolve(req, res); 542 } catch (RestException e) { 543 throw e; 544 } catch (Exception e) { 545 throw new BadRequest(e, 546 "Invalid data conversion. Could not convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.", 547 methodParams[i].getParamType().name(), methodParams[i].getName(), methodParams[i].getType(), method.getDeclaringClass().getName(), method.getName() 548 ); 549 } 550 } 551 552 try { 553 554 for (RestGuard guard : guards) 555 if (! guard.guard(req, res)) 556 return SC_OK; 557 558 Object output; 559 try { 560 output = method.invoke(context.getResource(), args); 561 if (res.getStatus() == 0) 562 res.setStatus(200); 563 if (! method.getReturnType().equals(Void.TYPE)) { 564 if (output != null || ! res.getOutputStreamCalled()) 565 res.setOutput(output); 566 } 567 } catch (InvocationTargetException e) { 568 Throwable e2 = e.getTargetException(); // Get the throwable thrown from the doX() method. 569 res.setStatus(500); 570 if (getResponseBodyMeta(e2) != null || getResponseBeanMeta(e2) != null) { 571 res.setOutput(e2); 572 } else { 573 throw e; 574 } 575 } 576 577 context.postCall(req, res); 578 579 if (res.hasOutput()) 580 for (RestConverter converter : converters) 581 res.setOutput(converter.convert(req, res.getOutput())); 582 583 } catch (IllegalArgumentException e) { 584 throw new BadRequest(e, 585 "Invalid argument type passed to the following method: ''{0}''.\n\tArgument types: {1}", 586 method.toString(), getReadableClassNames(args) 587 ); 588 } catch (InvocationTargetException e) { 589 Throwable e2 = e.getTargetException(); // Get the throwable thrown from the doX() method. 590 if (e2 instanceof RestException) 591 throw (RestException)e2; 592 if (e2 instanceof ParseException) 593 throw new BadRequest(e2); 594 if (e2 instanceof InvalidDataConversionException) 595 throw new BadRequest(e2); 596 throw e2; 597 } 598 return SC_OK; 599 } 600 601 @Override /* Object */ 602 public String toString() { 603 return "SimpleMethod: name=" + httpMethod + ", path=" + pathPattern.getPatternString(); 604 } 605 606 /* 607 * compareTo() method is used to keep SimpleMethods ordered in the RestCallRouter list. 608 * It maintains the order in which matches are made during requests. 609 */ 610 @Override /* Comparable */ 611 public int compareTo(RestJavaMethod o) { 612 int c; 613 614 c = priority.compareTo(o.priority); 615 if (c != 0) 616 return c; 617 618 c = pathPattern.compareTo(o.pathPattern); 619 if (c != 0) 620 return c; 621 622 c = compare(o.requiredMatchers.length, requiredMatchers.length); 623 if (c != 0) 624 return c; 625 626 c = compare(o.optionalMatchers.length, optionalMatchers.length); 627 if (c != 0) 628 return c; 629 630 c = compare(o.guards.length, guards.length); 631 if (c != 0) 632 return c; 633 634 return 0; 635 } 636 637 /** 638 * Bean property getter: <property>serializers</property>. 639 * 640 * @return The value of the <property>serializers</property> property on this bean, or <jk>null</jk> if it is not set. 641 */ 642 public SerializerGroup getSerializers() { 643 return serializers; 644 } 645 646 /** 647 * Bean property getter: <property>parsers</property>. 648 * 649 * @return The value of the <property>parsers</property> property on this bean, or <jk>null</jk> if it is not set. 650 */ 651 public ParserGroup getParsers() { 652 return parsers; 653 } 654 655 /** 656 * Bean property getter: <property>partSerializer</property>. 657 * 658 * @return The value of the <property>partSerializer</property> property on this bean, or <jk>null</jk> if it is not set. 659 */ 660 public HttpPartSerializer getPartSerializer() { 661 return partSerializer; 662 } 663 664 /** 665 * Bean property getter: <property>partParser</property>. 666 * 667 * @return The value of the <property>partParser</property> property on this bean, or <jk>null</jk> if it is not set. 668 */ 669 public HttpPartParser getPartParser() { 670 return partParser; 671 } 672 673 @Override /* Object */ 674 public boolean equals(Object o) { 675 if (! (o instanceof RestJavaMethod)) 676 return false; 677 return (compareTo((RestJavaMethod)o) == 0); 678 } 679 680 @Override /* Object */ 681 public int hashCode() { 682 return method.hashCode(); 683 } 684 685 //----------------------------------------------------------------------------------------------------------------- 686 // Utility methods. 687 //----------------------------------------------------------------------------------------------------------------- 688 static String[] resolveVars(VarResolver vr, String[] in) { 689 String[] out = new String[in.length]; 690 for (int i = 0; i < in.length; i++) 691 out[i] = vr.resolve(in[i]); 692 return out; 693 } 694 695 static HttpPartSerializer createPartSerializer(Class<? extends HttpPartSerializer> c, PropertyStore ps, HttpPartSerializer _default) { 696 HttpPartSerializer hps = ClassUtils.newInstance(HttpPartSerializer.class, c, true, ps); 697 return hps == null ? _default : hps; 698 } 699}