/*
Copyright (c) 2016 LinkedIn Corp.
Licensed 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 com.linkedin.restli.examples.greetings.server;
import com.linkedin.common.callback.Callback;
import com.linkedin.data.ByteString;
import com.linkedin.r2.message.stream.entitystream.ByteStringWriter;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.common.attachments.RestLiAttachmentDataSourceWriter;
import com.linkedin.restli.common.attachments.RestLiAttachmentReader;
import com.linkedin.restli.common.attachments.RestLiAttachmentReaderCallback;
import com.linkedin.restli.common.attachments.SingleRestLiAttachmentReaderCallback;
import com.linkedin.restli.examples.greetings.api.Greeting;
import com.linkedin.restli.server.CreateResponse;
import com.linkedin.restli.server.RestLiResponseAttachments;
import com.linkedin.restli.server.RestLiServiceException;
import com.linkedin.restli.server.UpdateResponse;
import com.linkedin.restli.server.annotations.Action;
import com.linkedin.restli.server.annotations.CallbackParam;
import com.linkedin.restli.server.annotations.RestLiAttachmentsParam;
import com.linkedin.restli.server.annotations.RestLiCollection;
import com.linkedin.restli.server.resources.CollectionResourceAsyncTemplate;
import java.io.ByteArrayOutputStream;
/**
* @author Karim Vidhani
*/
@RestLiCollection(name = "streamingGreetings", namespace = "com.linkedin.restli.examples.greetings.client")
public class StreamingGreetings extends CollectionResourceAsyncTemplate<Long, Greeting>
{
private static byte[] greetingBytes = "BeginningBytes".getBytes();
public StreamingGreetings()
{
}
@Override
public void get(Long key, @CallbackParam Callback<Greeting> callback)
{
if (getContext().responseAttachmentsSupported())
{
final GreetingWriter greetingWriter = new GreetingWriter(ByteString.copy(greetingBytes));
final RestLiResponseAttachments streamingAttachments =
new RestLiResponseAttachments.Builder().appendSingleAttachment(greetingWriter).build();
getContext().setResponseAttachments(streamingAttachments);
final String headerValue = getContext().getRequestHeaders().get("getHeader");
getContext().setResponseHeader("getHeader", headerValue);
callback.onSuccess(new Greeting().setMessage("Your greeting has an attachment since you were kind and "
+ "decided you wanted to read it!").setId(key));
}
callback.onError(new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, "You must be able to receive attachments!"));
}
public void create(Greeting entity, @CallbackParam Callback<CreateResponse> callback,
@RestLiAttachmentsParam RestLiAttachmentReader attachmentReader)
{
if (attachmentReader != null)
{
final String headerValue = getContext().getRequestHeaders().get("createHeader");
getContext().setResponseHeader("createHeader", headerValue);
attachmentReader.registerAttachmentReaderCallback(new GreetingBlobReaderCallback(callback));
return;
}
callback.onError(new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, "You must supply some attachments!"));
}
//The delete and update resource methods here are simply to show that although not typical, it is possible to return
//attachments from DELETE, UPDATE, PARTIAL_UPDATE, BATCH_DELETE, BATCH_UPDATE, and BATCH_PARTIAL_UPDATE. For the sake of
//brevity DELETE and UPDATE are used as examples.
@Override
public void delete(Long key, @CallbackParam Callback<UpdateResponse> callback)
{
respondWithResponseAttachment(callback);
}
@Override
public void update(Long key, Greeting entity, @CallbackParam Callback<UpdateResponse> callback)
{
respondWithResponseAttachment(callback);
}
private void respondWithResponseAttachment(final Callback<UpdateResponse> callback)
{
if (getContext().responseAttachmentsSupported())
{
//Echo the bytes back from the header
final String headerValue = getContext().getRequestHeaders().get("getHeader");
final GreetingWriter greetingWriter = new GreetingWriter(ByteString.copy(headerValue.getBytes()));
final RestLiResponseAttachments streamingAttachments =
new RestLiResponseAttachments.Builder().appendSingleAttachment(greetingWriter).build();
getContext().setResponseAttachments(streamingAttachments);
callback.onSuccess(new UpdateResponse(HttpStatus.S_200_OK));
}
callback.onError(new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, "You must be able to receive attachments!"));
}
@Action(name = "actionNoAttachmentsAllowed")
public int actionNoAttachmentsAllowed()
{
return 100;
}
@Action(name = "actionAttachmentsAllowedButDisliked")
public boolean actionAttachmentsAllowedButDisliked(final @RestLiAttachmentsParam RestLiAttachmentReader attachmentReader)
{
//Verify that null was passed in by returning true;
if (attachmentReader == null)
{
return true;
}
else
{
return false;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//For writing the response attachment
private static class GreetingWriter extends ByteStringWriter implements RestLiAttachmentDataSourceWriter
{
private GreetingWriter(final ByteString content)
{
super(content);
}
@Override
public String getAttachmentID()
{
return "12345";
}
}
//For reading in the request attachment
private static class GreetingBlobReaderCallback implements RestLiAttachmentReaderCallback
{
private final Callback<CreateResponse> _createResponseCallback;
private GreetingBlobReaderCallback(final Callback<CreateResponse> createResponseCallback)
{
_createResponseCallback = createResponseCallback;
}
@Override
public void onNewAttachment(RestLiAttachmentReader.SingleRestLiAttachmentReader singleRestLiAttachmentReader)
{
final SingleGreetingBlobReaderCallback singleGreetingBlobReaderCallback = new SingleGreetingBlobReaderCallback(this,
singleRestLiAttachmentReader);
singleRestLiAttachmentReader.registerCallback(singleGreetingBlobReaderCallback);
singleRestLiAttachmentReader.requestAttachmentData();
}
@Override
public void onFinished()
{
_createResponseCallback.onSuccess(new CreateResponse(150));
}
@Override
public void onDrainComplete()
{
_createResponseCallback.onError(new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR));
}
@Override
public void onStreamError(Throwable throwable)
{
_createResponseCallback.onError(new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR));
}
}
private static class SingleGreetingBlobReaderCallback implements SingleRestLiAttachmentReaderCallback
{
private final RestLiAttachmentReaderCallback _topLevelCallback;
private final RestLiAttachmentReader.SingleRestLiAttachmentReader _singleRestLiAttachmentReader;
private final ByteArrayOutputStream _byteArrayOutputStream = new ByteArrayOutputStream();
public SingleGreetingBlobReaderCallback(RestLiAttachmentReaderCallback topLevelCallback,
RestLiAttachmentReader.SingleRestLiAttachmentReader singleRestLiAttachmentReader)
{
_topLevelCallback = topLevelCallback;
_singleRestLiAttachmentReader = singleRestLiAttachmentReader;
}
@Override
public void onAttachmentDataAvailable(ByteString attachmentData)
{
try
{
_byteArrayOutputStream.write(attachmentData.copyBytes());
_singleRestLiAttachmentReader.requestAttachmentData();
}
catch (Exception exception)
{
_topLevelCallback.onStreamError(new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR));
}
}
@Override
public void onFinished()
{
greetingBytes = _byteArrayOutputStream.toByteArray();
}
@Override
public void onDrainComplete()
{
_topLevelCallback.onStreamError(new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR));
}
@Override
public void onAttachmentError(Throwable throwable)
{
//No need to do anything since the top level callback will get invoked with an error anyway
}
}
}