Package com.linkedin.restli.examples.greetings.server

Source Code of com.linkedin.restli.examples.greetings.server.GreetingsResourceImpl

/*
   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.Finder;
import com.linkedin.restli.server.annotations.Optional;
import com.linkedin.restli.server.annotations.PagingContextParam;
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<Long,Greeting>
{
  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<Long, Greeting> _db = Collections.synchronizedMap(new LinkedHashMap<Long, Greeting>());
  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, @QueryParam("isNullId") @Optional("false") boolean isNullId)
  {
    entity.setId(_idSeq.incrementAndGet());
    _db.put(entity.getId(), entity);
    if (isNullId)
    {
      return new CreateResponse(null, HttpStatus.S_201_CREATED);
    }
    return new CreateResponse(entity.getId());
  }

  @RestMethod.BatchGet
  public Map<Long, Greeting> batchGet(Set<Long> ids)
  {
    Map<Long, Greeting> batch = new HashMap<Long, Greeting>();
    Map<Long, RestLiServiceException> errors = new HashMap<Long, RestLiServiceException>();
    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<Long, Greeting>(batch, errors);
  }

  @RestMethod.BatchUpdate
  public BatchUpdateResult<Long, Greeting> batchUpdate(BatchUpdateRequest<Long, Greeting> entities)
  {
    Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
    for (Map.Entry<Long, Greeting> entry : entities.getData().entrySet())
    {
      responseMap.put(entry.getKey(), update(entry.getKey(), entry.getValue()));
    }
    return new BatchUpdateResult<Long, Greeting>(responseMap);
  }

  @RestMethod.BatchPartialUpdate
  public BatchUpdateResult<Long, Greeting> batchUpdate(BatchPatchRequest<Long, Greeting> entityUpdates)
  {
    Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
    for (Map.Entry<Long, PatchRequest<Greeting>> entry : entityUpdates.getData().entrySet())
    {
      responseMap.put(entry.getKey(), update(entry.getKey(), entry.getValue()));
    }
    return new BatchUpdateResult<Long, Greeting>(responseMap);
  }

  @RestMethod.BatchCreate
  public BatchCreateResult<Long, Greeting> batchCreate(BatchCreateRequest<Long, Greeting> entities)
  {
    List<CreateResponse> responses = new ArrayList<CreateResponse>(entities.getInput().size());

    for (Greeting g : entities.getInput())
    {
      responses.add(create(g, false));
    }
    return new BatchCreateResult<Long, Greeting>(responses);
  }

  @RestMethod.BatchDelete
  public BatchUpdateResult<Long, Greeting> batchDelete(BatchDeleteRequest<Long, Greeting> deleteRequest)
  {
    Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
    for (Long id : deleteRequest.getKeys())
    {
      responseMap.put(id, delete(id));
    }
    return new BatchUpdateResult<Long, Greeting>(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<Greeting> 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<Greeting> getAll(@PagingContextParam 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<Greeting> greetings = new ArrayList<Greeting>();
    for (Greeting greeting: _db.values())
    {
      if (greeting.getMessage().contains("GetAll"))
      {
        greetings.add(greeting);
      }
    }
    return greetings;
  }

  @Finder("searchWithDefault")
  public List<Greeting> searchWithDefault(@PagingContextParam PagingContext ctx,
                                          @QueryParam("tone") @Optional("FRIENDLY") Tone tone)
  {
    return search(ctx, tone);
  }

  @Finder("search")
  public List<Greeting> search(@PagingContextParam PagingContext ctx, @QueryParam("tone") @Optional Tone tone)
  {
    List<Greeting> greetings = new ArrayList<Greeting>();
    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<Greeting, Empty> searchWithPostFilter(@PagingContextParam PagingContext ctx)
  {
    List<Greeting> greetings = new ArrayList<Greeting>();
    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<Greeting, Empty>(greetings, total, null, PageIncrement.FIXED);
  }

  @Finder("searchWithTones")
  public List<Greeting> searchWithTones(@PagingContextParam PagingContext ctx, @QueryParam("tones") @Optional Tone[] tones)
  {
    Set<Tone> toneSet = new HashSet<Tone>(Arrays.asList(tones));
    List<Greeting> greetings = new ArrayList<Greeting>();
    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<Greeting, SearchMetadata> searchWithFacets(@PagingContextParam PagingContext ctx, @QueryParam("tone") @Optional Tone tone)
  {
    List<Greeting> greetings = search(ctx, tone);

    Map<Tone, Integer> toneCounts = new HashMap<Tone, Integer>();
    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<Tone, Integer> entry : toneCounts.entrySet())
    {
      ToneFacet f = new ToneFacet();
      f.setTone(entry.getKey());
      f.setCount(entry.getValue());
      metadata.getFacets().add(f);
    }

    return new CollectionResult<Greeting, SearchMetadata>(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<br>
   * resource level determines the granularity of the action<br>
   * 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");
  }
}
TOP

Related Classes of com.linkedin.restli.examples.greetings.server.GreetingsResourceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.