/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.systest.jaxrs;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.CompletionCallback;
import javax.ws.rs.container.ConnectionCallback;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.TimeoutHandler;
import javax.ws.rs.core.Response;
import org.apache.cxf.phase.PhaseInterceptorChain;
@Path("/bookstore")
public class BookContinuationStore implements BookAsyncInterface {
private Map<String, String> books = new HashMap<>();
private Executor executor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10));
public BookContinuationStore() {
init();
}
@GET
@Path("/books/defaulttimeout")
public void getBookDescriptionWithTimeout(@Suspended AsyncResponse async) {
async.register(new CallbackImpl());
async.setTimeout(2000, TimeUnit.MILLISECONDS);
}
@GET
@Path("/books/resume")
@Produces("text/plain")
public void getBookDescriptionImmediateResume(@Suspended AsyncResponse async) {
async.resume("immediateResume");
}
@GET
@Path("/books/resumeFromFastThread")
@Produces("text/plain")
public void getBookDescriptionResumeFromFastThread(@Suspended AsyncResponse async) {
executor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (Exception ex) {
// ignore
}
async.resume("resumeFromFastThread");
}
});
}
@GET
@Path("/books/nocontent")
@Produces("text/plain")
public void getBookNoContent(AsyncResponse async) {
async.resume(null);
}
public void getBookNoContentInterface(@Suspended AsyncResponse async) {
async.resume(Response.status(206).build());
}
@GET
@Path("/books/cancel")
public void getBookDescriptionWithCancel(@PathParam("id") String id,
@Suspended AsyncResponse async) {
PhaseInterceptorChain.getCurrentMessage().getClass();
async.setTimeout(2000, TimeUnit.MILLISECONDS);
async.setTimeoutHandler(new CancelTimeoutHandlerImpl());
}
@GET
@Path("/books/timeouthandler/{id}")
public void getBookDescriptionWithHandler(@PathParam("id") String id,
@Suspended AsyncResponse async) {
async.setTimeout(1000, TimeUnit.MILLISECONDS);
async.setTimeoutHandler(new TimeoutHandlerImpl(id, false));
}
@GET
@Path("/books/timeouthandlerresume/{id}")
public void getBookDescriptionWithHandlerResumeOnly(@PathParam("id") String id,
@Suspended AsyncResponse async) {
async.setTimeout(1000, TimeUnit.MILLISECONDS);
async.setTimeoutHandler(new TimeoutHandlerImpl(id, true));
}
@GET
@Path("/books/{id}")
public void getBookDescription(@PathParam("id") String id,
@Suspended AsyncResponse async) {
handleContinuationRequest(id, async);
}
@Path("/books/subresources/")
public BookContinuationStore getBookStore() {
return this;
}
@GET
@Path("{id}")
public void handleContinuationRequest(@PathParam("id") String id,
@Suspended AsyncResponse response) {
resumeSuspended(id, response);
}
@GET
@Path("books/notfound")
@Produces("text/plain")
public void handleContinuationRequestNotFound(@Suspended AsyncResponse response) {
response.register(new CallbackImpl());
resumeSuspendedNotFound(response);
}
@GET
@Path("books/notfound/unmapped")
@Produces("text/plain")
public void handleContinuationRequestNotFoundUnmapped(@Suspended AsyncResponse response) {
response.register(new CallbackImpl());
resumeSuspendedNotFoundUnmapped(response);
}
@GET
@Path("books/notfound/unmappedImmediate")
@Produces("text/plain")
public void handleUnmappedImmediate(@Suspended AsyncResponse response) throws BookNotFoundFault {
throw new BookNotFoundFault("");
}
@GET
@Path("books/mappedImmediate")
@Produces("text/plain")
public void handleMappedImmediate(@Suspended AsyncResponse response) throws BookNotFoundFault {
throw new WebApplicationException(Response.status(401).build());
}
@GET
@Path("books/unmappedFromFilter")
@Produces("text/plain")
public void handleContinuationRequestUnmappedFromFilter(@Suspended AsyncResponse response) {
response.register(new CallbackImpl());
response.resume(Response.ok().build());
}
@GET
@Path("books/suspend/unmapped")
@Produces("text/plain")
public void handleNotMappedAfterSuspend(@Suspended AsyncResponse response) throws BookNotFoundFault {
response.setTimeout(2000, TimeUnit.MILLISECONDS);
response.setTimeoutHandler(new CancelTimeoutHandlerImpl());
throw new BookNotFoundFault("");
}
@GET
@Path("/disconnect")
public void handleClientDisconnects(@Suspended AsyncResponse response) {
response.setTimeout(0, TimeUnit.SECONDS);
response.register(new ConnectionCallback() {
@Override
public void onDisconnect(AsyncResponse disconnected) {
System.out.println("ConnectionCallback: onDisconnect, client disconnects");
}
});
try {
Thread.sleep(3000);
} catch (InterruptedException ex) {
// ignore
}
response.resume(books.values().toString());
}
private void resumeSuspended(final String id, final AsyncResponse response) {
executor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
// ignore
}
response.resume(books.get(id));
}
});
}
private void resumeSuspendedNotFound(final AsyncResponse response) {
executor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
// ignore
}
response.resume(new NotFoundException());
}
});
}
private void resumeSuspendedNotFoundUnmapped(final AsyncResponse response) {
executor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
// ignore
}
response.resume(new BookNotFoundFault(""));
}
});
}
private void init() {
books.put("1", "CXF in Action1");
books.put("2", "CXF in Action2");
books.put("3", "CXF in Action3");
books.put("4", "CXF in Action4");
books.put("5", "CXF in Action5");
}
private class TimeoutHandlerImpl implements TimeoutHandler {
private boolean resumeOnly;
private String id;
private AtomicInteger timeoutExtendedCounter = new AtomicInteger();
TimeoutHandlerImpl(String id, boolean resumeOnly) {
this.id = id;
this.resumeOnly = resumeOnly;
}
@Override
public void handleTimeout(AsyncResponse asyncResponse) {
if (!resumeOnly && timeoutExtendedCounter.addAndGet(1) <= 2) {
asyncResponse.setTimeout(1, TimeUnit.SECONDS);
} else {
asyncResponse.resume(books.get(id));
}
}
}
private class CancelTimeoutHandlerImpl implements TimeoutHandler {
@Override
public void handleTimeout(AsyncResponse asyncResponse) {
asyncResponse.cancel(10);
}
}
private class CallbackImpl implements CompletionCallback {
@Override
public void onComplete(Throwable throwable) {
System.out.println("CompletionCallback: onComplete, throwable: " + throwable);
}
}
}