com.linkedin.restli.examples.greetings.server.GreetingsResourceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of restli-int-test-server Show documentation
Show all versions of restli-int-test-server Show documentation
Pegasus is a framework for building robust, scalable service architectures using dynamic discovery and simple asychronous type-checked REST + JSON APIs.
/*
Copyright (c) 2012 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.data.template.BooleanArray;
import com.linkedin.data.template.StringMap;
import com.linkedin.data.transform.DataProcessingException;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.common.PatchRequest;
import com.linkedin.restli.examples.greetings.api.Empty;
import com.linkedin.restli.examples.greetings.api.Greeting;
import com.linkedin.restli.examples.greetings.api.SearchMetadata;
import com.linkedin.restli.examples.greetings.api.Tone;
import com.linkedin.restli.examples.greetings.api.ToneFacet;
import com.linkedin.restli.examples.greetings.api.ToneFacetArray;
import com.linkedin.restli.examples.groups.api.TransferOwnershipRequest;
import com.linkedin.restli.server.BatchCreateRequest;
import com.linkedin.restli.server.BatchCreateResult;
import com.linkedin.restli.server.BatchDeleteRequest;
import com.linkedin.restli.server.BatchPatchRequest;
import com.linkedin.restli.server.BatchResult;
import com.linkedin.restli.server.BatchUpdateRequest;
import com.linkedin.restli.server.BatchUpdateResult;
import com.linkedin.restli.server.CollectionResult;
import com.linkedin.restli.server.CollectionResult.PageIncrement;
import com.linkedin.restli.server.CreateResponse;
import com.linkedin.restli.server.PagingContext;
import com.linkedin.restli.server.ResourceLevel;
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.ActionParam;
import com.linkedin.restli.server.annotations.Context;
import com.linkedin.restli.server.annotations.Finder;
import com.linkedin.restli.server.annotations.Optional;
import com.linkedin.restli.server.annotations.QueryParam;
import com.linkedin.restli.server.annotations.RestMethod;
import com.linkedin.restli.server.resources.BaseResource;
import com.linkedin.restli.server.resources.KeyValueResource;
import com.linkedin.restli.server.util.PatchApplier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
* Base class to various interfaces of a richer "Hello world" example, demonstrating a
* full array of methods, finders and actions. To circumvent inheritance diamonds, this
* class is intended to be used as a class member. Annotations are kept for reference.
*
* @author dellamag
*/
// package private
class GreetingsResourceImpl implements KeyValueResource
{
private static final String[] GREETINGS =
{ "Good morning!", "Guten Morgen!", "Buenos dias!", "Bon jour!", "Buon Giorno!" };
private static final Tone[] TONES = { Tone.FRIENDLY, Tone.SINCERE, Tone.INSULTING };
private static final Tone DEFAULT_TONE = Tone.INSULTING;
private static final int INITIAL_SIZE = 20;
private static final String[] INITIAL_MESSAGES = new String[INITIAL_SIZE];
private static final Tone[] INITIAL_TONES = new Tone[INITIAL_SIZE];
static {
// generate some "random" initial data
for (int i = 0; i < INITIAL_SIZE; i++)
INITIAL_MESSAGES[i] = GREETINGS[i % GREETINGS.length];
for (int i = 0; i < INITIAL_SIZE; i++)
INITIAL_TONES[i] = TONES[i % TONES.length];
}
private final AtomicLong _idSeq = new AtomicLong();
private final Map _db = Collections.synchronizedMap(new LinkedHashMap());
private final String _resourceName;
public GreetingsResourceImpl(String resourceName)
{
for (int i = 0; i < INITIAL_SIZE; i++)
{
Greeting g =
new Greeting().setId(_idSeq.incrementAndGet())
.setMessage(INITIAL_MESSAGES[i])
.setTone(INITIAL_TONES[i]);
_db.put(g.getId(), g);
}
_resourceName = resourceName;
}
// These CRUD annotations are MANDATORY for the code generator because we want to generate
// implementations which do not use the templates, e.g. Task
@RestMethod.Create
public CreateResponse create(Greeting entity)
{
entity.setId(_idSeq.incrementAndGet());
_db.put(entity.getId(), entity);
return new CreateResponse(entity.getId());
}
@RestMethod.BatchGet
public Map batchGet(Set ids)
{
Map batch = new HashMap();
Map errors = new HashMap();
for (long id : ids)
{
Greeting g = _db.get(id);
if (g != null)
{
batch.put(id, g);
}
else
{
errors.put(id, new RestLiServiceException(HttpStatus.S_404_NOT_FOUND));
}
}
return new BatchResult(batch, errors);
}
@RestMethod.BatchUpdate
public BatchUpdateResult batchUpdate(BatchUpdateRequest entities)
{
Map responseMap = new HashMap();
for (Map.Entry entry : entities.getData().entrySet())
{
responseMap.put(entry.getKey(), update(entry.getKey(), entry.getValue()));
}
return new BatchUpdateResult(responseMap);
}
@RestMethod.BatchPartialUpdate
public BatchUpdateResult batchUpdate(BatchPatchRequest entityUpdates)
{
Map responseMap = new HashMap();
for (Map.Entry> entry : entityUpdates.getData().entrySet())
{
responseMap.put(entry.getKey(), update(entry.getKey(), entry.getValue()));
}
return new BatchUpdateResult(responseMap);
}
@RestMethod.BatchCreate
public BatchCreateResult batchCreate(BatchCreateRequest entities)
{
List responses = new ArrayList(entities.getInput().size());
for (Greeting g : entities.getInput())
{
responses.add(create(g));
}
return new BatchCreateResult(responses);
}
@RestMethod.BatchDelete
public BatchUpdateResult batchDelete(BatchDeleteRequest deleteRequest)
{
Map responseMap = new HashMap();
for (Long id : deleteRequest.getKeys())
{
responseMap.put(id, delete(id));
}
return new BatchUpdateResult(responseMap);
}
@RestMethod.Get
public Greeting get(Long key)
{
return _db.get(key);
}
@RestMethod.Delete
public UpdateResponse delete(Long key)
{
boolean removed = _db.remove(key) != null;
return new UpdateResponse(removed ? HttpStatus.S_204_NO_CONTENT : HttpStatus.S_404_NOT_FOUND);
}
@RestMethod.PartialUpdate
public UpdateResponse update(Long key, PatchRequest patch)
{
Greeting g = _db.get(key);
if (g == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
try
{
PatchApplier.applyPatch(g, patch);
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
_db.put(key, g);
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
@RestMethod.Update
public UpdateResponse update(Long key, Greeting entity)
{
Greeting g = _db.get(key);
if (g == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
_db.put(key, entity);
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
@RestMethod.GetAll
public List getAll(@Context PagingContext ctx)
{
// Deterministic behaviour of getAll to make it easier to test as part of the integration test suite
// Just return those greetings that have "GetAll" present in their message
List greetings = new ArrayList();
for (Greeting greeting: _db.values())
{
if (greeting.getMessage().contains("GetAll"))
{
greetings.add(greeting);
}
}
return greetings;
}
@Finder("searchWithDefault")
public List searchWithDefault(@Context PagingContext ctx,
@QueryParam("tone") @Optional("FRIENDLY") Tone tone)
{
return search(ctx, tone);
}
@Finder("search")
public List search(@Context PagingContext ctx, @QueryParam("tone") @Optional Tone tone)
{
List greetings = new ArrayList();
int idx = 0;
int start = ctx.getStart();
int stop = start + ctx.getCount();
for (Greeting g : _db.values())
{
if (idx++ >= ctx.getStart())
{
if (tone == null || g.getTone().equals(tone))
{
greetings.add(g);
}
if (idx == stop)
{
break;
}
}
}
return greetings;
}
@Finder("searchWithPostFilter")
public CollectionResult searchWithPostFilter(@Context PagingContext ctx)
{
List greetings = new ArrayList();
int idx = 0;
int start = ctx.getStart();
int stop = start + ctx.getCount();
for (Greeting g : _db.values())
{
if (idx++ >= ctx.getStart())
{
greetings.add(g);
if (idx == stop)
{
break;
}
}
}
if(greetings.size() > 0) greetings.remove(0); // for testing, using a post-filter that just removes the first element
int total = _db.values().size();
// but we keep the numElements returned as the full count despite the fact that with the filter removed 1
// this is to keep paging consistent even in the presence of a post filter.
return new CollectionResult(greetings, total, null, PageIncrement.FIXED);
}
@Finder("searchWithTones")
public List searchWithTones(@Context PagingContext ctx, @QueryParam("tones") @Optional Tone[] tones)
{
Set toneSet = new HashSet(Arrays.asList(tones));
List greetings = new ArrayList();
int idx = 0;
int start = ctx.getStart();
int stop = start + ctx.getCount();
for (Greeting g : _db.values())
{
if (idx++ >= ctx.getStart())
{
if (tones == null || toneSet.contains(g.getTone()))
{
greetings.add(g);
}
if (idx == stop)
{
break;
}
}
}
return greetings;
}
@Finder("searchWithFacets")
public CollectionResult searchWithFacets(@Context PagingContext ctx, @QueryParam("tone") @Optional Tone tone)
{
List greetings = search(ctx, tone);
Map toneCounts = new HashMap();
for (Greeting g : greetings)
{
if (!toneCounts.containsKey(g.getTone()))
{
toneCounts.put(g.getTone(), 0);
}
toneCounts.put(g.getTone(), toneCounts.get(g.getTone()) + 1);
}
SearchMetadata metadata = new SearchMetadata();
metadata.setFacets(new ToneFacetArray());
for(Map.Entry entry : toneCounts.entrySet())
{
ToneFacet f = new ToneFacet();
f.setTone(entry.getKey());
f.setCount(entry.getValue());
metadata.getFacets().add(f);
}
return new CollectionResult(greetings, null, metadata);
}
private Greeting createGreeting()
{
return
new Greeting().setId(_idSeq.incrementAndGet())
.setMessage("This is a newly created greeting")
.setTone(DEFAULT_TONE);
}
// Some action examples
// These are not necessarily related to the greetings domain, but are here to give an idea as to how action work
/**
* Pretend to delete all greetings
* @return the number of greetings purged, always 100
*/
@Action(name = "purge")
public int purge()
{
return 100;
}
// complex types
@Action(name = "anotherAction")
public void anotherAction(@ActionParam("bitfield") BooleanArray bitfield,
@ActionParam("request") TransferOwnershipRequest transferReq,
@ActionParam("someString") String someString,
@ActionParam("stringMap") StringMap stringMap)
{
}
// optional/default
@Action(name = "someAction", resourceLevel = ResourceLevel.ENTITY)
public Greeting someAction(@ActionParam("a") @Optional("1") int a,
@ActionParam("b") @Optional("default") String b,
@ActionParam("c") @Optional TransferOwnershipRequest c,
@ActionParam("d") TransferOwnershipRequest d,
@ActionParam("e") Integer e)
{
return createGreeting();
}
/**
* a more concrete example of custom action
* resource level determines the granularity of the action
* mismatching the resource level in the request throws exception and will respond HTTP
* 400
*
* @param resource
* Instance of the resource class. This is not part of the action method and is
* needed because this implementation is not an actual resource.
*/
@Action(name = "updateTone", resourceLevel = ResourceLevel.ENTITY)
// The base resource parameter gets special handling in the generator. It is set to the actual
// resource class instance, and is not part of the generated REST method.
public Greeting updateTone(BaseResource resource,
@ActionParam("newTone") @Optional Tone newTone,
@ActionParam("delOld") @Optional("false") Boolean delOld)
{
// the way to get entity key in action
Long key = resource.getContext().getPathKeys().get(_resourceName + "Id");
Greeting g = _db.get(key);
if (g == null)
{
// HTTP 404
return g;
}
// delete existing Greeting and assign new key
if (delOld)
{
_db.remove(key);
key = _idSeq.incrementAndGet();
g.setId(key);
}
Tone t;
// newTone is an optional parameter
// omitting it in request results a null value
if (newTone == null)
{
t = DEFAULT_TONE;
}
else
{
t = newTone;
}
g.setTone(t);
_db.put(key, g);
return g;
}
@Action(name = "exceptionTest")
public void exceptionTest()
{
throw new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR, "Test Exception");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy