/*
* JBoss, Home of Professional Open Source
* Copyright 2015, Red Hat, Inc. and/or its affiliates, and individual
* contributors by the @authors tag. See the copyright.txt in the
* distribution for a full listing of individual contributors.
*
* 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 org.jboss.quickstarts.contact;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.spi.LoggableFailure;
import javax.ws.rs.WebApplicationException;
/**
* JAX-RS Example
* <p/>
* This class produces a RESTful service to read/write the contents of the contacts table.
*
* @author Joshua Wilson
*
*/
/*
* The Path annotation defines this as a REST Web Service using JAX-RS.
*
* By placing the Consumes and Produces annotations at the class level the methods all default to JSON. However, they
* can be overriden by adding the Consumes or Produces annotations to the individual method.
*
* It is Stateless to "inform the container that this RESTful web service should also be treated as an EJB and allow
* transaction demarcation when accessing the database." - Antonio Goncalves
*
*/
@Path("/contacts")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Stateless
public class ContactRESTService {
@Inject
private Logger log;
@Inject
private ContactService service;
@Context
private UriInfo uriInfo;
/**
* Search for and return all the Contacts. They are sorted alphabetically by name.
*
* @return List of Contacts
*/
@GET
public Response retrieveAllContacts() {
List<Contact> contacts = service.findAllOrderedByName();
return Response.ok(contacts).build();
}
/**
* Search for and return all the Contacts. They are sorted alphabetically by name.
*
* @return List of Contacts
*/
@GET
@Path("/{email}")
public Response retrieveContactsByEmail(@PathParam("email") String email) {
Contact contact = service.findByEmail(email);
if (contact == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return Response.ok(contact).build();
}
/**
* Search for just one Contact by it's ID.
*
* @param ID of the Contact
* @return Response
*/
@GET
@Path("/{id:[0-9][0-9]*}")
public Response retrieveContactById(@PathParam("id") long id) {
Contact contact = service.findById(id);
if (contact == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
log.info("findById " + id + ": found Contact = " + contact.getFirstName() + " " + contact.getLastName() + " " + contact.getEmail() + " " + contact.getPhoneNumber() + " "
+ contact.getBirthDate() + " " + contact.getId());
return Response.ok(contact).build();
}
/**
* Creates a new contact from the values provided. Performs validation and will return a JAX-RS response with either 200 (ok)
* or with a map of fields, and related errors.
*
* @param Contact
* @return Response
*/
@SuppressWarnings("unused")
@POST
public Response createContact(Contact contact) {
log.info("createContact started. Contact = " + contact.getFirstName() + " " + contact.getLastName() + " " + contact.getEmail() + " " + contact.getPhoneNumber() + " "
+ contact.getBirthDate() + " " + contact.getId());
if (contact == null) {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
Response.ResponseBuilder builder = null;
try {
// Go add the new Contact.
Contact created = service.create(contact);
// Construct a location of created contact.
URI location = null;
try {
UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder();
uriBuilder.path(Long.toString(created.getId()));
location = uriBuilder.build();
} catch (LoggableFailure lf) {
// IGNORED: UriInfo methods throw this if called outside the scope of request,
// that happens in ContactRegistrationTest.testRegister method.
}
// Create an CREATED Response and pass the URI location back in case it is needed.
builder = Response.created(location);
log.info("createContact completed. Contact = " + contact.getFirstName() + " " + contact.getLastName() + " " + contact.getEmail() + " " + contact.getPhoneNumber() + " "
+ contact.getBirthDate() + " " + contact.getId());
} catch (ConstraintViolationException ce) {
log.info("ConstraintViolationException - " + ce.toString());
// Handle bean validation issues
builder = createViolationResponse(ce.getConstraintViolations());
} catch (ValidationException e) {
log.info("ValidationException - " + e.toString());
// Handle the unique constrain violation
Map<String, String> responseObj = new HashMap<>();
responseObj.put("email", "That email is already used, please use a unique email");
builder = Response.status(Response.Status.CONFLICT).entity(responseObj);
} catch (Exception e) {
log.info("Exception - " + e.toString());
// Handle generic exceptions
Map<String, String> responseObj = new HashMap<>();
responseObj.put("error", e.getMessage());
builder = Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
}
return builder.build();
}
/**
* Updates a contact with the ID provided in the Contact. Performs validation, and will return a JAX-RS response with either 200 ok,
* or with a map of fields, and related errors.
*
* @param Contact
* @return Response
*/
@PUT
@Path("/{id:[0-9][0-9]*}")
public Response updateContact(@PathParam("id") long id, Contact contact) {
if (contact == null) {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
log.info("updateContact started. Contact = " + contact.getFirstName() + " " + contact.getLastName() + " " + contact.getEmail() + " " + contact.getPhoneNumber() + " "
+ contact.getBirthDate() + " " + contact.getId());
if (contact.getId() != id) {
// The client attempted to update the read-only Id. This is not permitted.
Response response = Response.status(Response.Status.CONFLICT).entity("The contact ID cannot be modified").build();
throw new WebApplicationException(response);
}
if (service.findById(contact.getId()) == null) {
// Verify if the contact exists. Return 404, if not present.
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
Response.ResponseBuilder builder = null;
try {
// Apply the changes the Contact.
service.update(contact);
// Create an OK Response and pass the contact back in case it is needed.
builder = Response.ok(contact);
log.info("updateContact completed. Contact = " + contact.getFirstName() + " " + contact.getLastName() + " " + contact.getEmail() + " " + contact.getPhoneNumber() + " "
+ contact.getBirthDate() + " " + contact.getId());
} catch (ConstraintViolationException ce) {
log.info("ConstraintViolationException - " + ce.toString());
// Handle bean validation issues
builder = createViolationResponse(ce.getConstraintViolations());
} catch (ValidationException e) {
log.info("ValidationException - " + e.toString());
// Handle the unique constrain violation
Map<String, String> responseObj = new HashMap<>();
responseObj.put("email", "That email is already used, please use a unique email");
responseObj.put("error", "This is where errors are displayed that are not related to a specific field");
responseObj.put("anotherError", "You can find this error message in /src/main/java/org/jboss/quickstarts/contact/ContactRESTService.java line 242.");
builder = Response.status(Response.Status.CONFLICT).entity(responseObj);
} catch (Exception e) {
log.info("Exception - " + e.toString());
// Handle generic exceptions
Map<String, String> responseObj = new HashMap<>();
responseObj.put("error", e.getMessage());
builder = Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
}
return builder.build();
}
/**
* Deletes a contact using the ID provided. If the ID is not present then nothing can be deleted, and will return a
* JAX-RS response with either 200 OK or with a map of fields, and related errors.
*
* @param Contact
* @return Response
*/
@DELETE
@Path("/{id:[0-9][0-9]*}")
public Response deleteContact(@PathParam("id") Long id) {
log.info("deleteContact started. Contact ID = " + id);
Response.ResponseBuilder builder = null;
try {
Contact contact = service.findById(id);
if (contact != null) {
service.delete(contact);
} else {
log.info("ContactRESTService - deleteContact - No contact with matching ID was found so can't Delete.");
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
builder = Response.noContent();
log.info("deleteContact completed. Contact = " + contact.getFirstName() + " " + contact.getLastName() + " " + contact.getEmail() + " " + contact.getPhoneNumber() + " "
+ contact.getBirthDate() + " " + contact.getId());
} catch (Exception e) {
log.info("Exception - " + e.toString());
// Handle generic exceptions
Map<String, String> responseObj = new HashMap<>();
responseObj.put("error", e.getMessage());
builder = Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
}
return builder.build();
}
/**
* Creates a JAX-RS "Bad Request" response including a map of all violation fields, and their message. This can be used
* by clients to show violations.
*
* @param violations A set of violations that needs to be reported
* @return JAX-RS response containing all violations
*/
private Response.ResponseBuilder createViolationResponse(Set<ConstraintViolation<?>> violations) {
log.fine("Validation completed. violations found: " + violations.size());
Map<String, String> responseObj = new HashMap<>();
for (ConstraintViolation<?> violation : violations) {
responseObj.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
}
}