Successfully reported this slideshow.
Your SlideShare is downloading. ×

Java EE 8 - February 2017 update

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
JAX-RS 2.1 Reloaded
JAX-RS 2.1 Reloaded
Loading in …3
×

Check these out next

1 of 65 Ad

More Related Content

Slideshows for you (20)

Similar to Java EE 8 - February 2017 update (20)

Advertisement

More from David Delabassee (20)

Recently uploaded (20)

Advertisement

Java EE 8 - February 2017 update

  1. 1. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Java EE 8 – What’s coming? February 2017 David Delabassee @delabassee Oracle
  2. 2. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Safe Harbor Statement The following is intended to outline our general product direcPon. It is intended for informaPon purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or funcPonality, and should not be relied upon in making purchasing decisions. The development, release, and Pming of any features or funcPonality described for Oracle’s products remains at the sole discrePon of Oracle. 2
  3. 3. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Java EE APIs - Backbone of Leading Open Source Projects 3 Java EE Containers Microservices Web Containers Web Frameworks PaaS REST
  4. 4. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 4 The reports of my death are greatly exaggerated. Java EE “
  5. 5. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 5 Life is not a quiet river... “ Charles Regimbeau
  6. 6. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 6 Community Survey h`ps://blogs.oracle.com/theaquarium/entry/java_ee_8_community_survey2
  7. 7. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Java EE 8 7
  8. 8. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JAX-RS 2.1 •  ReacPve Client API •  Server-sent events •  Non-blocking I/O in providers (filters, interceptors…) •  Hypermedia API enhancements •  IntegraPon with other JSRs and frameworks 8
  9. 9. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 9 JAX-RS 2.0 Client client = ClientBuilder.newClient(); String name = client.target("http://example.com/api/hello") .request(MediaType.TEXT_PLAIN) .get(String.class); //... client.close();
  10. 10. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 10 JAX-RS 2.0 // http://example.com/api/read/doe?dpt=1 WebTarget myResource = client.target("http://example.com/api/read") .path("{user}") .resolveTemplate("user", "joe") .queryParam("dpt", "1"); Response response = myResource.request(...).get(); // ...
  11. 11. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 11 JAX-RS 2.0 Client client = ClientBuilder.newClient(); WebTarget myResource = client.target("http://example.com/api/read"); Future<String> response = myResource.request(MediaType.TEXT_PLAIN) .async() .get(String.class); // ...
  12. 12. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 12 JAX-RS 2.0 WebTarget myResource = client.target("http://example.com/api/read"); Future<Customer> fCustomer = myResource.request(MediaType.TEXT_PLAIN) .async() .get(new InvocationCallback<Customer>() { @Override public void completed(Customer customer) { // work on the customer } @Override public void failed(Throwable throwable) { // Oops! } });
  13. 13. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 13 JAX-RS 2.0
  14. 14. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 14 JAX-RS 2.0 destination.path("recommended").request() .header("Rx-User", "Async") .async() .get(new InvocationCallback<List<Destination>>() { @Override public void completed(final List<Destination> recommended) { final CountDownLatch innerLatch = new CountDownLatch(recommended.size()); final Map<String, Forecast> forecasts = Collections.synchronizedMap(new HashMap<>()); for (final Destination dest : recommended) { forecast.resolveTemplate("dest", dest.getDestination()).request() .async() .get(new InvocationCallback<Forecast>() { @Override public void completed(final Forecast forecast) { forecasts.put(dest.getDestination(), forecast); innerLatch.countDown(); }
  15. 15. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 15 JAX-RS 2.0 … @Override public void failed(final Throwable throwable) { innerLatch.countDown(); } }); } try { if (!innerLatch.await(10, TimeUnit.SECONDS)) { // timeout } } catch (final InterruptedException e) { // Ooops, interrupted! } // Continue with processing… } @Override public void failed(final Throwable throwable) { // Recommendation error } });
  16. 16. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 16 JAX-RS 2.1 // JAX-RS 2.1 CompletionStage<Response> csResponse = ClientBuilder.newClient() .target("http://example.com/api") .request() .rx() .get(); Future<Response> fResponse = ClientBuilder.newClient() .target("http://example.com/api") .request() .async() .get(); Response response = ClientBuilder.newClient() .target("http://example.com/api") .request() .get();
  17. 17. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 17 JAX-RS 2.1 CompletionStage<String> cs1 = client.target("http://partner.us/api") .request() .rx() .get(String.class); CompletionStage<String> cs2 = client.target("http://supplier.be/api") .request() .rx() .get(String.class); // get both responses in a List (when they are available) CompletionStage<List<String>> listCompletionStage = cs1.thenCombine(cs2, List::of);
  18. 18. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JAX-RS 2.1 •  Server-Sent Events – Persistent, one-way communicaPon channel – Text protocol, special media type "text/event-stream" – Server can send mulPple messages (events) to a client – Can contain id, name, comment retry interval – Supported in all modern browsers 18
  19. 19. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JAX-RS 2.1 19 SSE – Server side classes •  OutboundSseEvent – Outbound SSE representaPon •  SseEventOutput extends Flow.Subscriber<OutboundSseEvent> – Outbound SSE stream – ApplicaPon can write events to it, check its state and close it •  SseBroadcaster extends Flow.Publisher<OutboundSseEvent> – Used to work with mulPple SseEventOutput instances •  SseContext – Injectable factory for the above classes
  20. 20. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JAX-RS 2.1 20 SSE – Client side classes •  InboundSseEvent – Inbound Server Sent Event – Similar to OutboundSseEvent •  SseEventInput – Low-level way for accessing Server Sent Events stream •  SseEventSource extends Flow.Publisher<InboundSseEvent> – Used for subscribing to Server Sent Event streams
  21. 21. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 21 JAX-RS 2.1 // server-side init @Inject SseContext sseContext; SseBroadcaster sseBroadcaster = sseContext.newBroadcaster(); sseBroadcaster.onException( (o, e) -> { e.printStackTrace(); } ); sseBroadcaster.onClose(...); @GET @Path ("sse") @Produces (MediaTypes.SERVER_SENT_EVENTS) public SseEventOutput itemEvents(...) { SseEventOutput eventOutput = sseContext.newOutput(); //... return eventOutput; }
  22. 22. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 22 JAX-RS 2.1 //... final OutboundSseEvent = ose sseContext.newEvent() .id(...) .data(...) .comment(...) .build(); eventOutput.write(ose); sseBroadcaster.subscribe(eventOutput); sseBroadcaster.broadcast(ose);
  23. 23. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 23 JAX-RS 2.1 private final WebTarget target = ClientBuilder.newClient().target("http://host/sse"); private SseEventSource eventSource; public void connect(Consumer<String> dataConsumer) { eventSource = SseEventSource.target(target) .open(es -> es.subscribe(new Flow.Subscriber<InboundSseEvent>() { @Override public void onNext(final InboundSseEvent item) { dataConsumer.accept(item.readData()); } // onSubscribe, onError, onComplete })); } //... eventSource.close();
  24. 24. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-P 1.1 •  Update JSON-P spec to stay current with emerging standards – RFC 7159 - The JavaScript Object NotaPon (JSON) Data Interchange Format – RFC 6901 – JSON Pointer – RFC 6902 – JSON Patch – RFC 7396 – JSON Merge Patch •  Add ediPng/transformaPon operaPons to JsonObject and JsonArray •  Add JSON Collectors •  Big JSON support 24
  25. 25. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-P 1.1 •  JSON-Pointer – IETF RFC 6901 •  String syntax for referencing a JSON value E.g. "/0/user/address" 25
  26. 26. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-P 1.1 26 [ { "name":"Duke", "gender":"M", "phones":{ "home":"555-123-456", "mobile":"555-321-456"}}, { "name":"Jane", "gender":"F", "phones":{ "mobile":"555-999-111"}} ] JsonArray contacts = … JsonPointer pointer = new JsonPointer("/0/name"); JsonValue value = pointer.getValue(contacts);
  27. 27. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-P 1.1 •  JSON-Pointer – IETF RFC 6901 •  String syntax for referencing a JSON value – E.g. "/0/user/address” •  Methods – getValue(), add(), replace(), remove() 27 JsonPointer pointer = new JsonPointer("/0/name"); JsonArray result = pointer.replace(contacts, Json.createValue("Joe"));
  28. 28. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-P 1.1 •  JSON-Patch – IETF RFC 6902 •  Patch is a JSON document – Array of objects / operaPons for modifying a JSON document – Add, replace, remove, move, copy, test 28
  29. 29. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 29 JSON-P 1.1 [ { "op" : "replace", "path" : "/0/phones/mobile", "value" : "650-111-2222" } , { "op" : "remove", "path" : "/1" } ] [ { "name" : "Duke", "gender" : "M", "phones" : { "home" : "650-123-4567", "mobile" : "650-234-5678" } } , { "name" : "Jane", "gender" : "F", "phones" : { "mobile" : "707-555-9999“ } } ]
  30. 30. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 30 JSON-P 1.1 [ { "op" : "replace", "path" : "/0/phones/mobile", "value" : "650-111-2222" } , { "op" : "remove", "path" : "/1" } ] [ { "name" : "Duke", "gender" : "M", "phones" : { "home" : "650-123-4567", "mobile" : "650-234-5678" } } , { "name" : "Jane", "gender" : "F", "phones" : { "mobile" : "707-555-9999“ } } ]
  31. 31. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 31 JSON-P 1.1 [ { "op" : "replace", "path" : "/0/phones/mobile", "value" : "650-111-2222" } , { "op" : "remove", "path" : "/1" } ] [ { "name" : "Duke", "gender" : "M", "phones" : { "home" : "650-123-4567", "mobile" : "650-111-2222" } } , { "name" : "Jane", "gender" : "F", "phones" : { "mobile" : "707-555-9999“ } } ]
  32. 32. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 32 JSON-P 1.1 [ { "op" : "replace", "path" : "/0/phones/mobile", "value" : "650-111-2222" } , { "op" : "remove", "path" : "/1" } ] [ { "name" : "Duke", "gender" : "M", "phones" : { "home" : "650-123-4567", "mobile" : "650-111-2222" } } , { "name" : "Jane", "gender" : "F", "phones" : { "mobile" : "707-555-9999“ } } ]
  33. 33. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 33 JSON-P 1.1 [ { "op" : "replace", "path" : "/0/phones/mobile", "value" : "650-111-2222" } , { "op" : "remove", "path" : "/1" } ] [ { "name" : "Duke", "gender" : "M", "phones" : { "home" : "650-123-4567", "mobile" : "650-111-2222" } } , { "name" : "Jane", "gender" : "F", "phones" : { "mobile" : "707-555-9999“ } } ]
  34. 34. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 34 JSON-P 1.1 [ { "op" : "replace", "path" : "/0/phones/mobile", "value" : "650-111-2222" } , { "op" : "remove", "path" : "/1" } ] [ { "name" : "Duke", "gender" : "M", "phones" : { "home" : "650-123-4567", "mobile" : "650-111-2222" } } ]
  35. 35. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 35 JSON-P 1.1 JsonArray target = …; JsonArray patch = …; JsonPatch jsonPatch = new JsonPatch(patch); JsonArray result = jsonPatch.apply(target); JsonPatchBuilder builder = new JsonPatchBuilder(); JsonArray result = builder.add("/Joe/phones/office", "1234-567") .remove("/Amy/age") .apply(target);
  36. 36. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 36 JSON-P 1.1 Orignal Patch Result {"a" : "b"} {"a" : "c"} {"a" : "c"} {"a" : "b"} {"b" : "c"} {"a" : "b", "b" : "c"} {"a" : "b"} {"a" : null} { } {"a" : "b", {"a" : null} {"b" : "c"} "b" : "c"} … •  JSON-Merge Patch – IETF RFC 7386
  37. 37. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 37 JSON-P 1.1 •  JSON-Merge Patch – IETF RFC 7386 JsonObject contact = …; // The target to be patched JsonObject patch = …; JsonValue output = JsonMergePatch.mergePatch(target, patch);
  38. 38. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 38 JSON-P 1.1 •  JSON-Patch & JSON-Merge Patch Diff JsonValue output = JsonMergePatch.diff(original, target); JsonArray diff = JsonPatch.diff(original, target);
  39. 39. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 39 JSON-P 1.1 JsonArray cars = ...; JsonArray audis = cars.getValuesAs(JsonObject.class).stream() .filter(x -> "Audi".equals(x.getString("brand"))) .map(x -> (x.getString("serial"))) .collect( Collector.of( () -> Json.createArrayBuilder(), (builder, value) -> builder.add(value), (builder1, builder2) -> builder1.add(builder2), builder -> builder.build() ) );
  40. 40. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 40 JSON-P 1.1 JsonArray cars = ...; JsonArray audis = cars.getValuesAs(JsonObject.class).stream() .filter( x->"Audi".equals(x.getString("brand")) ) .map( x->( x.getString("serial") ) .collect( JsonCollectors.toJsonArray() );
  41. 41. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-B 1.0 •  JAXB-like API to marshal/unmarshal Java objects to/from JSON documents •  Default mapping between classes and JSON •  CustomizaPon APIs – AnnotaPon (@JsonbProperty, @JsonbNillable) – RunPme configuraPon builder •  Allows to swap providers •  Natural follow on to JSON-P – closes the JSON support gap 41
  42. 42. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 42 JSON-B 1.0 [ { "brand" : "Apple", "model" : "iPhone 7", "stock" : 1 }, { "brand" : "Samsung", "model" : "Galaxy Note 7", "stock" : 666 } ] Phone phone1 = new Phone(); phone1.setBrand("Apple"); phone1.setModel("iPhone 7"); phone1.setStock(1); … List<Phone> inventory = new ArrayList<>(); inventory.add(phone1); inventory.add(phone2); Jsonb jsonb = JsonBuilder.create(); String stock = jsonb.toJson(inventory);
  43. 43. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 43 public class Customer { private int id; @JsonbProperty("name") private String firstName; } public class Customer { public int id; public String firstName; @JsonbProperty("name") private String getFirstName() { return firstName; } } JSON-B 1.0 CustomizaPons
  44. 44. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-B 1.0 CustomizaPons •  Naming strategies – IDENTITY : myProp – LOWER_CASE_WITH_DASHES : my-prop – LOWER_CASE_WITH_UNDERSCORES : my_prop – UPPER_CAMEL_CASE : MyProp – UPPER_CAMEL_CASE_WITH_SPACES : My Prop – CASE_INSENSITIVE : myprop – Or a custom implementaPon 44 •  Property ordering – Any, Lexicographical, Reverse •  Binary Data Strategies – Base64, Base64 URL, Byte
  45. 45. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSON-B 1.0 CustomizaPons •  Property naming •  Property ordering •  Property to ignore •  Null handling •  Custom instanPaPon 45 •  Fields visibility •  Date/Number Formats •  Binary Encoding •  Adapters •  ...
  46. 46. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 46 JSON-B 1.0 CustomizaPons Jsonb jsonb = JsonbBuilder.create(); //Ordering, naming strategy, encoding, Locale, … JsonbConfig config = new JsonbConfig() .withFormatting(true) .withAdapters(new CarAdapter()); Jsonb jsonb = JsonbBuilder.create(config); Jsonb jsonb = JsonBuilder.newBuilder("myProvider");
  47. 47. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Servlet 4.0 •  Support for HTTP/2 – Request/response mulPplexing – Server push – Upgrade from HTTP 1.1 •  CompaPbility with latest HTTP 1.1 RFCs •  Smaller community-requested improvements (JIRA issues) 47
  48. 48. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | HTTP/2 •  Binary Framing over single TCP connecPon •  Request/Response mulPplexing •  Stream PrioriPzaPon •  Server Push •  Upgrade from HTTP 1.1 •  Header Compression •  Preserve HTTP semanPc •  Flow Control 48
  49. 49. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Servlet 4.0 49 PushBuilder builder = baseRequest.getPushBuilder(); builder.addHeader("X-Pusher", …); builder.path(aResource) .etag(associated._etag) .lastModified(associated._lastModified) .push();
  50. 50. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | JSF 2.3 •  Be`er CDI IntegraPon – Way more things are injectable – Finally marking legacy managed beans as deprecated •  Java Time support •  WebSocket IntegraPon •  Ajax Method InvocaPon •  Class Level Bean ValidaPon •  UIData and UIRepeat improvements 50
  51. 51. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | CDI 2.0 •  Define behavior of CDI outside of a Java EE container •  API to bootstrap a CDI container in Java SE •  Spec split into 3 parts: – CDI Core – CDI for Java SE – CDI for Java EE •  Apply Interceptor on Producer •  Observers ordering •  Asynchronous events 51
  52. 52. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | CDI 2.0 52 @Inject Event<PaymentEvent> debitEvent; // producer debitEvent.fire(somePayload); // consumer public void anObesrver(@Observes Payload p) { … }
  53. 53. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | CDI 2.0 53 // consumer A public void anObesrver(@Observes Payload p) { … } // consumer B public void anotherObesrver(@Observes Payload p) { … }
  54. 54. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | CDI 2.0 54 // consumer A public void anObesrver(@Observes @Priority(10) Payload p) { … } // consumer B public void anotherObesrver(@Observes @Priority(20) Payload p) { … }
  55. 55. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | CDI 2.0 55 @Inject Event<PaymentEvent> debitEvent; // async producer debitEvent.fireAsync(somePayload); // async consumer public void anObesrver(@ObservesAsync Payload p) { … }
  56. 56. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Bean ValidaPon 2.0 •  Embrace Java SE 8 – Support for new Date/Time API – Constraints applied to collecPon elements – OpPonal wrappers – Repeatable annotaPons •  Introduce new constraints – E.g. @NotEmpty, @NotBlank •  AddiPonal features requested from community 56
  57. 57. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Bean ValidaPon 2.0 57 List<@NotNull @Email String> emails; String @NotNull @Email[] emails; Map<@Valid Customer, @Valid Address> primaryAddressByCustomer; Optional<@Past LocalDate> getRegistrationDate(); @ZipCode(countryCode = "fr", groups = Default.class, message = "unvalid zip")! @ZipCode(countryCode = "fr", groups = SuperUser.class, message = "zip nok…")! private String zipCode;
  58. 58. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Security API for Java EE •  IdenPty Store – Standardize applicaPon-accessible IdenPty Store – Validate Caller's credenPals and accessing related idenPty a`ributes •  Used by an authenPcaPon mechanism (e.g. JASPIC) •  AuthenPcaPon Mechanism – Simplify applicaPon-accessible authenPcaPon mechanisms •  Security Context – Standardize a pla{orm-wide Security Context •  ProgrammaPc access point 58
  59. 59. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Wrap-up 59
  60. 60. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Java EE 8 60 Connector JAXB JSP Debugging Managed Beans JSP Concurrency EE Interceptors JAX-WS WebSocket Bean ValidaPon JASPIC Servlet JMS JTA Deployment Batch JACC Dependency InjecPon JAXR JSTL Management CDI EJB JAX-RPC Web Services JSF JPA Common AnnotaPons EL JAX-RS Web Services Metadata JavaMail CDI JSON-B Security Bean ValidaPon JSF JAX-RS JSON-P Servlet JSP (*) MRs Releases not listed
  61. 61. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 61 Java EE 8 - Status SpecificaKons Early DraM Public DraM CDI 2.0 – JSR 365 Ongoing Java EE 8 – JSR 366 2nd dra} JSON-B 1.0 – JSR 367 Done! Servlet 4.0 – JSR 369 Ongoing JAX-RS 2.1 – JSR 370 Expected Q1 Expected Q2 JSF 2.3 – JSR 372 Ongoing JSON-P 1.1 – JSR 374 Ongoing Security 1.0 – JSR 375 Expected Q1 Beans ValidaPon 2.0 – JSR 380 Expected Q1 MVC – JSR 371 : Spec Lead transfer in progress
  62. 62. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Java EE 8 - Summary •  Work in progress – H2 2017 Final Release •  Contribute! – Adopt A JSR •  h`ps://community.oracle.com/community/java/jcp/adopt-a-jsr •  Open Source Reference ImplementaPons – GlassFish 5, Jersey … •  h`p://glassfish.org 62
  63. 63. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 63 SomePmes you have to take a step back to move forward. “ Erika Taylor
  64. 64. Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 64 Tack!

×