I am implementing Jersey based REST API and using swagger to generate HTML based documentation for the same. I am using swagger's annotations to read and scan the resources to generate documentation. I have specified response for each resource using #ApiResponse annotation as below:
#Path("/hello")
#Api(value = "Hello World" )
public class HelloRest
{
#GET
#ApiOperation(value="Hello world", httpMethod="GET")
#ApiResponses(value={ #ApiResponse(code = 200, message = "Success", response = WebservicesErrorResponse.class, reference = "C:/Desktop/hello.json")
#ApiResponse(code = 404, message = "Not found", response = WebservicesErrorResponse.class)})
#Produces({"application/json", "application/xml"})
public Response helloWorld()
{
return Response.status(WebservicesCommonTypes.SUCCESS).entity("Hello rest API").build();
}
}
It is working fine and it is generating HTML based documentation as below:
As it shows the complete structure (Model and example value) of response if response code is 404. And in example value, it is not showing the values, only showing the type for each parameter for the model.
I want to show the sample example schema for the response so that client can understand that what would be the exact response for each response. I researched on it and I found that there is one attribute:
#ApiResponse(reference = "") - Specifies a reference to the response type. The specified reference can be either local or remote and will be used as-is, and will override any specified response() class.
I tried it and I give it a path for my sample.json file as below:
#ApiResponse(code = 200, message = "Success", response = WebServicesErrorResponse, reference = "http://localhost:9001/myinstanceofapplication/html/api-doc/hello.json")
and I also tried to give another path that is local path like below:
#ApiResponse(code = 200, message = "Success", response = WebservicesErrorResponse.class, reference = "C:/Desktop/hello.json")
but when swagger generate document for it then it gives following:
It is showing C:/Desktop/hello.json is not defined!
I have researched and tried lot many solutions but couldn't able to give proper reference to it. I found that this is an issue by https://github.com/swagger-api/swagger-ui/issues/1700 and https://github.com/swagger-api/swagger-js/issues/606.
So how can I use reference attribute of #ApiResponse to that swagger could show the sample XML/JSON swagger UI. My model class is below:
#XmlRootElement(name="response")
#XmlAccessorType(XmlAccessType.FIELD)
public class WebservicesErrorResponse
{
#XmlElement
private int code;
#XmlElement
private String message;
public WebservicesErrorResponse(){ }
public WebservicesErrorResponse(int code, String message)
{
this.code = code;
this.message = message;
}
public int getCode()
{
return code;
}
public void setCode(int code)
{
this.code = code;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
}
and I want to show following sample XML in the swagger UI:
<?xml version="1.0"?>
<response>
<code>200</code>
<message>success</message>
</response>
You need to annotate your model class (not the API resource/method!) with the #ApiModel and #ApiModelProperty annotations as described here.
For what you want to achieve, it would probably be enough to annotate your model members as follows:
#ApiModelProperty(example = "200")
#XmlElement
private int code;
#ApiModelProperty(example = "success")
#XmlElement
private String message;
If that doesn't work, try putting the annotation on the getters (I'm not really familiar with the XML side of this, have only done it for JSON).
Related
I'm passing a model to an API action with a property called eventType which is a nullable custom enum.
If I pass a random value for eventType, such as 'h', it fails to serialise which is correct.
However, the error I get from the ModelState is not something I would want a public caller to see. It includes the line number and position (see below).
I've tried a number of options including a custom data annotation with no success.
Does anyone know how I could define a nicer custom message?
"Error converting value \"h\" to type
'System.Nullable`1[Custom.EventTypes]'. Path 'eventType', line 1,
position 80."
Most times the first error is usually the most important error or rather one that describes the situation properly. You can use this way to manipulate to get the first error message from the first key or change it to whatever you want if you wish to get all the error messages.
public ActionResult GetMyMoney(MyModel myModel)
{
string nss = ModelState.First().Key;
ModelError[] ern = ModelState[nss].Errors.ToArray();
string ndd = ern.First().ErrorMessage;
}
public class CustomFilter: IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
// You can pass custom object to BadRequestObjectResult method
context.Result = new BadRequestObjectResult(customObject);
}
}
}
You can write a custom filter like above mentioned and pass a custom object with your message.
Ref: this
IF you just want the error messages you can simply create a custom class of response and then
var response = new ResponseApi{
StatusCode = HttpStatusCode.BadRequest,
Message = "Validation Error",
Response = ModelState.Values.SelectMany(x => x.Errors).Select(x =>
x.ErrorMessage)
};
then just return the response or create a validation filter to handle validations globally.
/// <summary>
/// Validatation filter to validate all the models.
/// </summary>
public class ValidationActionFilter : ActionFilterAttribute
{
/// <inheritdoc/>
public override void OnActionExecuting(HttpActionContext actionContext)
{
ModelStateDictionary modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
actionContext.Response = SendResponse(new ResponseApi
{
StatusCode= 400,
Message = "Validation Error",
Response = modelState.Values.SelectMany(x =>
x.Errors).Select(x => x.ErrorMessage)
});
}
}
private HttpResponseMessage SendResponse(ResponseApiresponse)
{
var responseMessage = new HttpResponseMessage
{
StatusCode = (HttpStatusCode)response.StatusCode,
Content = new StringContent(JsonConvert.SerializeObject(response)),
};
responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return responseMessage;
}
}
I'm developing a REST API using Spring Cloud, Spring Data JPA, Spring Data Rest and Spring Boot. The server implementation generates the various data items correctly, according to the HAL spec and so forth. It generates the HAL+JSON as follows:
{
"lastModifiedBy" : "unknown",
"lastModifiedOn" : "2015-06-04T12:19:45.249688",
"id" : 2,
"name" : "Item 2",
"description" : null,
"_links" : {
"self" : {
"href" : "http://localhost:8080/pltm/accounts/2"
},
"customers" : {
"href" : "http://localhost:8080/pltm/accounts/2/customers"
},
"users" : {
"href" : "http://localhost:8080/pltm/accounts/2/users"
},
"groups" : {
"href" : "http://localhost:8080/pltm/accounts/2/groups"
}
}
}
Now I'm trying to implement the client implementation, using the FeignClient Spring Cloud library. I've defined my client interface as follows.
#FeignClient("serviceId")
#RequestMapping(value = "/api", consumes = MediaType.APPLICATION_JSON_VALUE)
public interface PltmClient
{
// Account Requests
#RequestMapping(method = RequestMethod.GET, value = "/accounts")
PagedResources<Resource<Account>> getAccounts();
#RequestMapping(method = RequestMethod.GET, value = "/accounts/{id}")
Resource<Account> getAccountAsResource(#PathVariable("id") Long id);
#RequestMapping(method = RequestMethod.GET, value = "/accounts/{id}")
Account getAccount(#PathVariable("id") Long id);
}
When I call the getAccout() method, I get back my domain object Account with the details from the JSON document. That object is a simple POJO. All of the fields are filled in properly.
public class Account
{
private Long id;
private String name;
private String description;
/** Setters/Getters left out for brevity **/
}
But when I call the getAccountAsResource(), it works and I get back a Resource object that has the data. However, the call to Resource.getContent() returns an Account object that is not completely filled out. In this case, the Account.getId() is NULL, which is causing problems.
Any ideas why this is happening? One idea I had is that the Resource class defines a getId() method, and that is somehow confusing the Jackson ObjectMapper.
The larger question is if this whole method is viable or is there a better way? Obviously, I could just use the plain POJO as my return type, but that looses the HAL information on the client side.
Has anyone successfully implemented a Java-based client implementation for Spring Data REST server endpoints?
Account not having an id is a feature of spring-data-rest. You need to enable populating of the id on the server side.
I also add a different method to return the id (in Account)
#JsonProperty("accountId")
public String getId() {
return id;
}
Enable id
#Configuration
public class MyConfig extends RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration( RepositoryRestConfiguration config) {
config.exposeIdsFor(Account.class);
}
}
I have used it here: https://github.com/spencergibb/myfeed, though I believe where I use resources, I'm using RestTemplate.
Reading the documentation in Breeze website, to retrieve a single entity have to use the fetchEntityByKey.
manager.fetchEntityByKey(typeName, id, true)
.then(successFn)
.fail(failFn)
Problem 1: Metadata
When trying to use this method, an error is displayed because the metadata has not yet been loaded. More details about the error here.
The result is that whenever I need to retrieve a single entity, have to check if the metadata is loaded.
manager = new breeze.EntityManager(serviceName);
successFn = function(xhr) {}
failFn = function(xhr) {};
executeQueryFn = function() {
return manager.fetchEntityByKey(typeName, id, true).then(successFn).fail(failFn);
};
if (manager.metadataStore.isEmpty()) {
return manager.fetchMetadata().then(executeQueryFn).fail(failFn);
} else {
return executeQueryFn();
}
Question
How can I extend the breeze, creating a Get method to check if metadata is loaded, and if not, load it?
Problem 2: OData and EntitySetController
I would use the OData standard (with EntitySetController) in my APIs.
This page in Breeze documentation shows how, then follow this tutorial to deploy my app with OData.
The problem as you can see here and here, is that the EntitySetController follows the odata pattern, to retrieve an entity must use odata/entity(id), or to retrieve all entities you can use `odata/entity'.
Example
In controller:
[BreezeController]
public class passosController : EntitySetController<Passo>
{
[HttpGet]
public string Metadata()
{
return ContextProvider.Metadata();
}
[HttpGet, Queryable(AllowedQueryOptions = AllowedQueryOptions.All, PageSize = 20)]
public override IQueryable<T> Get()
{
return Repositorio.All();
}
[HttpGet]
protected override T GetEntityByKey(int key)
{
return Repositorio.Get(key);
}
}
When I use:
manager = new breeze.EntityManager("/odata/passos");
manager.fetchEntityByKey("Passo", 1, true)
.then(successFn)
.fail(failFn)
The url generated is: /odata/passos/Passos?$filter=Id eq 1
The correct should be: /odata/passos(2)
Question
How can I modify Breeze for when use fetchEntityByKey to retrieve entity use odata/entity(id)?
We have two RESTful APIs - one is internal and another one is public, the two being implemented by different jars. The public API sort of wraps the internal one, performing the following steps:
Do some work
Call internal API
Do some work
Return the response to the user
It may happen (though not necessarily) that the two jars run in the same Java process.
We are using Restlet with the JAX-RS extension.
Here is an example of a simple public API implementation, which just forwards to the internal API:
#PUT
#Path("abc")
public MyResult method1(#Context UriInfo uriInfo, InputStream body) throws Exception {
String url = uriInfo.getAbsolutePath().toString().replace("/api/", "/internalapi/");
RestletClientResponse<MyResult> reply = WebClient.put(url, body, MyResult.class);
RestletUtils.addResponseHeaders(reply.responseHeaders);
return reply.returnObject;
}
Where WebClient.put is:
public class WebClient {
public static <T> RestletClientResponse<T> put(String url, Object body, Class<T> returnType) throws Exception {
Response restletResponse = Response.getCurrent();
ClientResource resource = new ClientResource(url);
Representation reply = null;
try {
Client timeoutClient = new Client(Protocol.HTTP);
timeoutClient.setConnectTimeout(30000);
resource.setNext(timeoutClient);
reply = resource.put(body, MediaType.APPLICATION_JSON);
T result = new JacksonConverter().toObject(new JacksonRepresentation<T>(reply, returnType), returnType, resource);
Status status = resource.getStatus();
return new RestletClientResponse<T>(result, (Form)resource.getResponseAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS), status);
} finally {
if (reply != null) {
reply.release();
}
resource.release();
Response.setCurrent(restletResponse);
}
}
}
and RestletClientResponse<T> is:
public class RestletClientResponse<T> {
public T returnObject = null;
public Form responseHeaders = null;
public Status status = null;
public RestletClientResponse(T returnObject, Form responseHeaders, Status status) {
this.returnObject = returnObject;
this.responseHeaders = responseHeaders;
this.status = status;
}
}
and RestletUtils.addResponseHeaders is:
public class RestletUtils {
public static void addResponseHeader(String key, Object value) {
Form responseHeaders = (Form)org.restlet.Response.getCurrent().getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
if (responseHeaders == null) {
responseHeaders = new Form();
org.restlet.Response.getCurrent().getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, responseHeaders);
}
responseHeaders.add(key, value.toString());
}
public static void addResponseHeaders(Form responseHeaders) {
for (String headerKey : responseHeaders.getNames()) {
RestletUtils.addResponseHeader(headerKey, responseHeaders.getValues(headerKey));
}
}
}
The problem is that if the two jars run in the same Java process, then an exception thrown from the internal API is not routed to the JAX-RS exception mapper of the internal API - the exception propagates up to the public API and is translated to the Internal Server Error (500).
Which means I am doing it wrong. So, my question is how do I invoke the internal RESTful API from within the public API implementation given the constraint that both the client and the server may run in the same Java process.
Surely, there are other problems, but I have a feeling that fixing the one I have just described is going to fix others as well.
The problem has nothing to do with the fact that both internal and public JARs are in the same JVM. They are perfectly separated by WebResource.put() method, which creates a new HTTP session. So, an exception in the internal API doesn't propagate to the public API.
The internal server error in the public API is caused by the post-processing mechanism, which interprets the output of the internal API and crashes for some reason. Don't blame the internal API, it is perfectly isolated and can't cause any troubles (even though it's in the same JVM).
...or "How to determine which WCF method will be called based on URI?"
In a WCF service, suppose a method is invoked and I have the URI that was used to invoke it. How can I get information about the WCF end point, method, parameters, etc. that the URI maps to?
[OperationContract]
[WebGet(UriTemplate = "/People/{id}")]
public Person GetPersonByID(int id)
{
//...
}
For instance, if the URI is: GET http://localhost/Contacts.svc/People/1, I want to get this information: service name (Service), Method (GetPersonByID), Parameters (PersonID=1). The point is to be able to listen for the request and then extract the details of the request in order to track the API call.
The service is hosted via http. This information is required before the .Net caching can kick in so each call (whether cached or not) can be tracked. This probably means doing this inside HttpApplication.BeginRequest.
FYI I'm hoping to not use reflection. I'd like to make use of the same methods WCF uses to determine this. E.g. MagicEndPointFinder.Resolve(uri)
Here is what I ended up doing, still interested if there is a cleaner way!
REST
private static class OperationContractResolver
{
private static readonly Dictionary<string, MethodInfo> RegularExpressionsByMethod = null;
static OperationContractResolver()
{
OperationContractResolver.RegularExpressionsByMethod = new Dictionary<string, MethodInfo>();
foreach (MethodInfo method in typeof(IREST).GetMethods())
{
WebGetAttribute attribute = (WebGetAttribute)method.GetCustomAttributes(typeof(WebGetAttribute), false).FirstOrDefault();
if (attribute != null)
{
string regex = attribute.UriTemplate;
//Escape question marks. Looks strange but replaces a literal "?" with "\?".
regex = Regex.Replace(regex, #"\?", #"\?");
//Replace all parameters.
regex = Regex.Replace(regex, #"\{[^/$\?]+?}", #"[^/$\?]+?");
//Add it to the dictionary.
OperationContractResolver.RegularExpressionsByMethod.Add(regex, method);
}
}
}
public static string ExtractApiCallInfo(string relativeUri)
{
foreach (string regex in OperationContractResolver.RegularExpressionsByMethod.Keys)
if (Regex.IsMatch(relativeUri, regex, RegexOptions.IgnoreCase))
return OperationContractResolver.RegularExpressionsByMethod[regex].Name;
return null;
}
}
SOAP
private static void TrackSoapApiCallInfo(HttpContext context)
{
string filePath = Path.GetTempFileName();
string title = null;
//Save the request content. (Unfortunately it can't be written to a stream directly.)
context.Request.SaveAs(filePath, false);
//If the title can't be extracted then it's not an API method call, ignore it.
try
{
//Read the name of the first element within the SOAP body.
using (XmlReader reader = XmlReader.Create(filePath))
{
if (!reader.EOF)
{
XmlNamespaceManager nsManager = new XmlNamespaceManager(reader.NameTable);
XDocument document = XDocument.Load(reader);
//Need to add the SOAP Envelope namespace to the name table.
nsManager.AddNamespace("s", "http://schemas.xmlsoap.org/soap/envelope/");
title = document.XPathSelectElement("s:Envelope/s:Body", nsManager).Elements().First().Name.LocalName;
}
}
//Delete the temporary file.
File.Delete(filePath);
}
catch { }
//Track the page view.
}