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.mock2; 014 015import java.util.*; 016 017import org.apache.juneau.json.*; 018import org.apache.juneau.marshall.*; 019import org.apache.juneau.parser.*; 020import org.apache.juneau.rest.annotation.*; 021import org.apache.juneau.rest.client.*; 022import org.apache.juneau.http.remote.*; 023import org.apache.juneau.serializer.*; 024 025/** 026 * Creates a mocked interface against a REST resource class to use for creating test remote resource interfaces. 027 * 028 * <ul class='seealso'> 029 * <li class='link'>{@doc juneau-rest-mock.MockRemote} 030 * </ul> 031 * 032 * @param <T> The interface class. 033 */ 034public class MockRemote<T> { 035 036 private MockRest.Builder mrb; 037 private RestClientBuilder rcb = RestClient.create().json(); 038 private final Class<T> intf; 039 040 /** 041 * Constructor. 042 * 043 * @param intf 044 * The remote interface annotated with {@link Remote @Remote}. 045 * @param impl 046 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 047 * <br>If a class, it must have a no-arg constructor. 048 */ 049 protected MockRemote(Class<T> intf, Object impl) { 050 this.intf = intf; 051 mrb = MockRest.create(impl); 052 } 053 054 /** 055 * Create a new builder using the specified remote resource interface and REST implementation bean or bean class. 056 * 057 * <p> 058 * Uses {@link JsonSerializer#DEFAULT} and {@link JsonParser#DEFAULT} for serializing and parsing by default. 059 * 060 * 061 * @param intf 062 * The remote interface annotated with {@link Remote @Remote}. 063 * @param impl 064 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 065 * <br>If a class, it must have a no-arg constructor. 066 * @return A new builder. 067 */ 068 public static <T> MockRemote<T> create(Class<T> intf, Object impl) { 069 return new MockRemote<>(intf, impl); 070 } 071 072 /** 073 * Create a new builder using the specified remote resource interface and REST implementation bean or bean class. 074 * 075 * <p> 076 * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default. 077 * 078 * @param intf 079 * The remote interface annotated with {@link Remote @Remote}. 080 * @param impl 081 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 082 * <br>If a class, it must have a no-arg constructor. 083 * @param m 084 * The marshall to use for serializing and parsing the HTTP bodies. 085 * @return A new builder. 086 */ 087 public static <T> MockRemote<T> create(Class<T> intf, Object impl, Marshall m) { 088 return new MockRemote<>(intf, impl).marshall(m); 089 } 090 091 /** 092 * Create a new builder using the specified remote resource interface and REST implementation bean or bean class. 093 * 094 * <p> 095 * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default. 096 * 097 * @param intf 098 * The remote interface annotated with {@link Remote @Remote}. 099 * @param impl 100 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 101 * <br>If a class, it must have a no-arg constructor. 102 * @param s 103 * The serializer to use for serializing request bodies. 104 * <br>Can be <jk>null</jk> to force no serializer to be used and no <c>Content-Type</c> header. 105 * @param p 106 * The parser to use for parsing response bodies. 107 * <br>Can be <jk>null</jk> to force no parser to be used and no <c>Accept</c> header. 108 * @return A new builder. 109 */ 110 public static <T> MockRemote<T> create(Class<T> intf, Object impl, Serializer s, Parser p) { 111 return new MockRemote<>(intf, impl).serializer(s).parser(p); 112 } 113 114 /** 115 * Constructs a remote proxy interface based on the settings of this builder. 116 * 117 * @return A new remote proxy interface. 118 */ 119 public T build() { 120 MockRest mr = mrb.build(); 121 return rcb.httpClientConnectionManager(new MockHttpClientConnectionManager(mr)).rootUrl("http://localhost").headers(mr.getHeaders()).build().getRemote(intf); 122 } 123 124 /** 125 * Convenience method for getting a remote resource interface. 126 * 127 * <p> 128 * Uses {@link JsonSerializer#DEFAULT} and {@link JsonParser#DEFAULT} for serializing and parsing by default. 129 * 130 * <p> 131 * Equivalent to calling the following: 132 * <p class='bcode w800'> 133 * MockRemoteResource.<jsm>create</jsm>(intf, impl).build(); 134 * </p> 135 * 136 * @param intf 137 * The remote interface annotated with {@link Remote @Remote}. 138 * @param impl 139 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 140 * <br>If a class, it must have a no-arg constructor. 141 * @return A new proxy interface. 142 */ 143 public static <T> T build(Class<T> intf, Object impl) { 144 return create(intf, impl).build(); 145 } 146 147 /** 148 * Convenience method for getting a remote resource interface. 149 * 150 * <p> 151 * Uses the serializer and parser defined on the specified marshall for serializing and parsing by default. 152 * 153 * <p> 154 * Equivalent to calling the following: 155 * <p class='bcode w800'> 156 * MockRemoteResource.<jsm>create</jsm>(intf, impl).marshall(m).build(); 157 * </p> 158 * 159 * @param intf 160 * The remote interface annotated with {@link Remote @Remote}. 161 * @param impl 162 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 163 * <br>If a class, it must have a no-arg constructor. 164 * @param m 165 * The marshall to use for serializing request bodies and parsing response bodies. 166 * <br>Can be <jk>null</jk> to force no serializer or parser to be used and no <c>Accept</c> or <c>Content-Type</c> header. 167 * @return A new proxy interface. 168 */ 169 public static <T> T build(Class<T> intf, Object impl, Marshall m) { 170 return create(intf, impl).marshall(m).build(); 171 } 172 173 /** 174 * Convenience method for getting a remote resource interface. 175 * 176 * <p> 177 * Uses the specified serializer and parser for serializing and parsing by default. 178 * 179 * <p> 180 * Equivalent to calling the following: 181 * <p class='bcode w800'> 182 * MockRemoteResource.<jsm>create</jsm>(intf, impl).serializer(s).parser(p).build(); 183 * </p> 184 * 185 * @param intf 186 * The remote interface annotated with {@link Remote @Remote}. 187 * @param impl 188 * The REST implementation bean or bean class annotated with {@link Rest @Rest}. 189 * <br>If a class, it must have a no-arg constructor. 190 * @param s 191 * The serializer to use for serializing request bodies. 192 * <br>Can be <jk>null</jk> to force no serializer to be used and no <c>Content-Type</c> header. 193 * @param p 194 * The parser to use for parsing response bodies. 195 * <br>Can be <jk>null</jk> to force no parser to be used and no <c>Accept</c> header. 196 * @return A new proxy interface. 197 */ 198 public static <T> T build(Class<T> intf, Object impl, Serializer s, Parser p) { 199 return create(intf, impl).serializer(s).parser(p).build(); 200 } 201 202 /** 203 * Enable debug mode. 204 * 205 * @return This object (for method chaining). 206 */ 207 public MockRemote<T> debug() { 208 mrb.debug(); 209 rcb.debug(); 210 return this; 211 } 212 213 /** 214 * Adds a header to every request. 215 * 216 * @param name The header name. 217 * @param value 218 * The header value. 219 * <br>Can be <jk>null</jk> (will be skipped). 220 * @return This object (for method chaining). 221 */ 222 public MockRemote<T> header(String name, Object value) { 223 mrb.header(name, value); 224 rcb.header(name, value); 225 return this; 226 } 227 228 /** 229 * Adds the specified headers to every request. 230 * 231 * @param value 232 * The header values. 233 * <br>Can be <jk>null</jk> (existing values will be cleared). 234 * <br><jk>null</jk> null map values will be ignored. 235 * @return This object (for method chaining). 236 */ 237 public MockRemote<T> headers(Map<String,Object> value) { 238 mrb.headers(value); 239 rcb.headers(value); 240 return this; 241 } 242 243 /** 244 * Adds an <c>Accept</c> header to every request. 245 * 246 * @param value The <c>Accept</c> header value. 247 * @return This object (for method chaining). 248 */ 249 public MockRemote<T> accept(String value) { 250 mrb.accept(value); 251 rcb.accept(value); 252 return this; 253 } 254 255 /** 256 * Adds a <c>Content-Type</c> header to every request. 257 * 258 * @param value The <c>Content-Type</c> header value. 259 * @return This object (for method chaining). 260 */ 261 public MockRemote<T> contentType(String value) { 262 mrb.contentType(value); 263 rcb.contentType(value); 264 return this; 265 } 266 267 /** 268 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/json"</js>. 269 * 270 * @return This object (for method chaining). 271 */ 272 public MockRemote<T> json() { 273 marshall(Json.DEFAULT); 274 return this; 275 } 276 277 /** 278 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/json+simple"</js>. 279 * 280 * @return This object (for method chaining). 281 */ 282 public MockRemote<T> simpleJson() { 283 marshall(SimpleJson.DEFAULT); 284 return this; 285 } 286 287 /** 288 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/xml"</js>. 289 * 290 * @return This object (for method chaining). 291 */ 292 public MockRemote<T> xml() { 293 marshall(Xml.DEFAULT); 294 return this; 295 } 296 297 /** 298 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/html"</js>. 299 * 300 * @return This object (for method chaining). 301 */ 302 public MockRemote<T> html() { 303 marshall(Html.DEFAULT); 304 return this; 305 } 306 307 /** 308 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/plain"</js>. 309 * 310 * @return This object (for method chaining). 311 */ 312 public MockRemote<T> plainText() { 313 marshall(PlainText.DEFAULT); 314 return this; 315 } 316 317 /** 318 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"octal/msgpack"</js>. 319 * 320 * @return This object (for method chaining). 321 */ 322 public MockRemote<T> msgpack() { 323 marshall(MsgPack.DEFAULT); 324 return this; 325 } 326 327 /** 328 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/uon"</js>. 329 * 330 * @return This object (for method chaining). 331 */ 332 public MockRemote<T> uon() { 333 marshall(Uon.DEFAULT); 334 return this; 335 } 336 337 /** 338 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"application/x-www-form-urlencoded"</js>. 339 * 340 * @return This object (for method chaining). 341 */ 342 public MockRemote<T> urlEnc() { 343 marshall(UrlEncoding.DEFAULT); 344 return this; 345 } 346 347 /** 348 * Convenience method for setting <c>Accept</c> and <c>Content-Type</c> headers to <js>"text/openapi"</js>. 349 * 350 * @return This object (for method chaining). 351 */ 352 public MockRemote<T> openapi() { 353 marshall(OpenApi.DEFAULT); 354 return this; 355 } 356 357 /** 358 * Associates the specified {@link Marshall} with this client. 359 * 360 * <p> 361 * This is shorthand for calling <c>serializer(x)</c> and <c>parser(x)</c> using the inner 362 * serializer and parser of the marshall object. 363 * 364 * @param value 365 * The marshall to use for serializing and parsing HTTP bodies. 366 * <br>Can be <jk>null</jk> (will remote the existing serializer/parser). 367 * @return This object (for method chaining). 368 */ 369 public MockRemote<T> marshall(Marshall value) { 370 if (value != null) 371 serializer(value.getSerializer()).parser(value.getParser()); 372 else 373 serializer(null).parser(null); 374 return this; 375 } 376 377 /** 378 * Associates the specified {@link Serializer} with this client. 379 * 380 * @param value 381 * The serializer to use for serializing HTTP bodies. 382 * <br>Can be <jk>null</jk> (will remote the existing serializer). 383 * @return This object (for method chaining). 384 */ 385 public MockRemote<T> serializer(Serializer value) { 386 rcb.serializer(value); 387 contentType(value == null ? null : value.getPrimaryMediaType().toString()); 388 return this; 389 } 390 391 /** 392 * Associates the specified {@link Parser} with this client. 393 * 394 * @param value 395 * The parser to use for parsing HTTP bodies. 396 * <br>Can be <jk>null</jk> (will remote the existing parser). 397 * @return This object (for method chaining). 398 */ 399 public MockRemote<T> parser(Parser value) { 400 rcb.parser(value); 401 accept(value == null ? null : value.getPrimaryMediaType().toString()); 402 return this; 403 } 404}