Grails REST 404 when hitting anything other than index - rest

When following http://grails.org/doc/latest/guide/webServices.html#restfulControllers in order to create RESTful webservices, I am getting 404 error when I hit anything other than index.
in my Bootstrap.groovy I have
def init = { servletContext ->
new Restaurant(title:"mourne seafood").save()
new Restaurant(title:"RBG").save()
}
in my Restaurant.groovy domain class i have
class Restaurant {
String title
static constraints = {
}
}
and in my RestaurantController.groovy REST controller I have
import grails.rest.*;
class RestaurantController extends RestfulController {
static responseFormats = ['json', 'xml']
RestaurantController() {
super(Restaurant)
}
}
I thought when reading the above link that if I call
GET <domain>/restaurant
It would call the index method, which is fine, this works, however when I call
GET <domain>/restaurant/1
I thought it should call the show method with 1 as the id? However I am getting a 404. It works correctly when I hit GET <domain>/restaurant/show/2 am I wrong in thinking that when the docs say
GET /books/${id} show in the mapping table that I shouldnt have to explicitly put show in the URL?

I know It sucks but here it is:
You need to create a generic rest controller and annotate the domain class pointing to it.
First delete the RestaurantController.groovy
Then create a BaseRestController (inside the src/main/groovy folder)
src/main/groovy/BaseRestController.groovy
import grails.rest.*;
class BaseRestController<T> extends RestfulController<T> {
BaseRestController(Class<T> domainClass) {
this(domainClass, false)
}
BaseRestController(Class<T> domainClass, boolean readOnly) {
super(domainClass, readOnly)
}
#Override
def show() {
println 'showing...'
respond queryForResource(params.id)
}
}
Ps. I just override the index action to show it works
Now you can annotate the domain class
grails-app/domain/Restaurant.groovy
import grails.rest.*
#Resource(uri='/restaurants', formats=['json', 'xml'], superClass=BaseRestController)
class Restaurant {
String title
static constraints = {
}
}
You need to specify the formats in the domain class and not in the controller. Otherwise it will be ignored and XML will be default.
I named the api endpoint as restaurants instead of the grails default restaurant
Now you can HTTP your RestFull app e.g. http://localhost:8080/restaurants/1

Related

Expected default behavior for Grails RESTful mapping to Nested Resources

I have my Grails Domain classes annotated with #Resource with the uri specifications in UrlMappings where I declare the resource nesting. But even though according to https://docs.grails.org/latest/guide/theWebLayer.html#restfulMappings it seems that just declaring this the right way, I should have the correct behavior that I wanted, which is that a URL pattern such as /nesting/1/nested will list the nested domain that belonged to the nesting domain with ID 1, the observed behavior is that it just lists out all nested domain objects.
So for that, my workaround is to have a controller implemented that overrides the listResources to filter the nested domain by the nesting domain. But what's weird to me is why I even have to do that at all. The documentation said it defaults to the index action but said index action seems to just behave as if it's the index() of nested (without taking nesting into account).
My domain entities are WeightSensor:
#Resource(formats = ['json', 'xml'])
class WeightSensor extends Sensor<WeightData>
{
Set<WeightData> data
static constraints = {
}
}
its superclass Sensor
#Resource(formats = ['json', 'xml'])
class Sensor<T extends SensorData>
{
Set<T> data
static hasMany = [data: SensorData]
String name
static constraints = {
name unique: true
}
}
and WeightData
class WeightData extends SensorData
{
Float weight
static constraints = {
weight nullable: false
}
}
and its superclass SensorData
class SensorData
{
#BindingFormat('yyyy-MM-dd HH:mm:ss.S') // 2019-07-11 22:00:28.909
Date timestamp
static belongsTo = [sensor: Sensor]
static constraints = {
timestamp nullable: false
}
}
In my UrlMappings I have the following:
"/sensor/weight"(resources: 'weightSensor') {
"/data"(resources: "weightData")
}
My WeightDataController extends from a SensorDataController:
class WeightDataController extends SensorDataController<WeightSensor, WeightData>
{
#SuppressWarnings("GroovyUnusedDeclaration")
static responseFormats = ['json', 'xml']
WeightDataController()
{
super(WeightData, WeightSensor, "weightSensorId")
}
}
And SensorDataController in turn extends RestfulController, and overrides the listAllResources method as below.
import grails.rest.RestfulController
class SensorDataController<S extends Sensor, T extends SensorData> extends RestfulController<T>
{
String idProperty
Class<S> sensorType
#SuppressWarnings("GroovyUnusedDeclaration")
static responseFormats = ['json', 'xml']
protected SensorDataController(Class<T> dataType, Class<S> sensorType, String idProperty)
{
super(dataType)
this.idProperty = idProperty
this.sensorType = sensorType
}
#Override
protected List<T> listAllResources(Map params)
{
Long sensorId = params.get(idProperty) as Long
if (sensorId)
{
resource.withCriteria() {
eq 'sensor.id', sensorId
maxResults params.max ?: 10
firstResult params.offset ?: 0
} as List<T>
}
else
{
super.listAllResources(params)
}
}
}
Note because in order for me to have my WeightDataController class be used, I needed to remove the #Resource on top of WeightData domain entity above, another nice little gem of wisdom I had to discover with trial and error.
I can probably blame this on the fact that the documentation for nested resources seems a bit open to interpretation. But when we see in the documentation a URL like GET books/${bookId}/authors, doesn't that look like it should return the list of Author objects that belongs to the Book instance IDed by bookId?
I know that I'm not alone as I did find this online of someone asking the same question I have - https://gist.github.com/mnellemann/7cfff1c721ef32f0be6c63574795f795 but no one answered them either. I also came across another SO post nested RESTful resources that was abandoned 5 years ago as well.
But 3 people having the same question and no one responding to our questions (I asked mine on the Grails Slack community) usefully because there is a work-around is not acceptable. At the risk of having my question taken down for a slew of different reasons, I question the usefulness of even having the grails nested resource URL mapping in the first place because I could have done everything manually myself without having to "declare" such a nesting in UrlMappings.
In closing, what I'm trying to find out is whether or not there's more "configuration" I need to do to get Grails nested Resources to behave in the way that I expected, which is how the documentation painted, correctly. Because just doing what was described doesn't get me that.

Grails 3 Restful Link Generator (w/o the action)

Is there a way to reconfigure the Grails 3 Link Generator to create Restful links, i.e. localhost:8080/book/{id} rather than the old style that includes the action in the URL, localhost:8080/book/show/{id}?
I'd like to have restful URLs in the location headers of the responses to save actions.
I've been using this Grails Restful Link Generator as a workaround. I'm not perfectly happy with it, but it's the best I've been able to come up with thus far.
1. Create a trait in src/main/groovy that removes the superfluous action from the URL
import grails.web.mapping.LinkGenerator
trait RestfulLinkGeneratorTrait {
LinkGenerator grailsLinkGenerator
String generateLink(Map map) {
map.controller = map.controller ?: this.controllerName
map.absolute = map.absolute ?: true
map.action = map.action ?: "show"
grailsLinkGenerator.link(map).replace("/$map.action", "")
}
}
2. Implement the RestfulLinkGenerator on your controller(s) and call generateLink(id: obj.id) to generate links.
#Secured('ROLE_USER')
class BookController extends RestfulController implements RestfulLinkGeneratorTrait {
//... other methods ...//
#Transactional
def save() {
// ... save you resource ... //
response.addHeader(HttpHeaders.LOCATION, generateLink(id: book.id))
respond book, [status: CREATED, view: 'show']
}
//... other methods ...//
}

Web Api 2 Inheritance No route providing a controller name was found to match request URI

Basically I cannot get my Web Api 2 application to work.
First of all here are my requirements.
In my application I am creating a dozen of controllers ( ProductController, ItemController, SalesController...etc). There are 2 actions which are absolutely common in all my controllers:
FetchData, PostData
(Each controller then may implement a number of other methods which are sepcific to its business domain )
Instead of repeating these actions in every controllers like:
public class ProductController:ApiController{
[HttpPost]
public MyReturnJson FetchData( MyJsonInput Input){
....
return myJsonResult;
}
}
public class SalesController:ApiController{
[HttpPost]
public MyReturnJson FetchData( MyJsonInput Input){
....
return myJsonResult;
}
}
I decided to create a base controller MyBaseController:
public class MyBaseController : ApiController{
[HttpPost]
public MyReturnJson FetchData( MyJsonInput Input){
....
return myJsonResult;
}
}
with the 2 methods so every other controller would inherit them (It saves me from repeating them in every controller). The common base class has been defined and implemented in a separate assembly which is then referenced in my web project.
Then in my javascript client (using breeze) I call a specific controller like
breeze.EntityQuery.from('FetchData')
where my serviceName is 'my_api/product/'
(in the WebApiConfig, the routing table has been defined like:
config.Routes.MapHttpRoute(
name: "my_api",
routeTemplate: "my_api/{controller}/{action}"
);
But when the javascript code is executed I get the error message:
No route providing a controller name was found to match request URI
http://localhost:xxxxx/my_api/product/FetchData
If I don't use a common base class but instead repeat this method (FetchData) in every class (basically ProductController inherits directly from ApiController and not from MyBaseController) every thing works fine and my method is hit. I thing there is a problem with the inheritance scheme. Maybe there is something I don't get (first time using Web Api 2) or some constraints (routing, configuration...) I do not respect. Right now I am stuck and I would appreciate any suggestion which might point me to the right direction. Is inheritance allowed in Web Api 2?
I am not sure why your code is not working. But in the next link (http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#ARI) you can see an example of inheritance using attribute routing.
This is the code example:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
I hope that it helps.

Using my own service with Laravel4

In my app, I was testing Google Directions API with ajax, but since I was just testing all the logic was in the routes.php file. Now I want to do things the proper way and have three layers: route, controller and service.
So in the routes I tell Laravel which method should be executed:
Route::get('/search', 'DirectionsAPIController#search');
And the method just returns what the service is supposed to return:
class DirectionsAPIController extends BaseController {
public function search() {
$directionsSearchService = new DirectionsSearchService();
return $directionsSearchService->search(Input::all());
}
}
I created the service in app/libraries/Services/Directions and called it DirectionsSearchService.php and copied all the logic I developed in routes:
class DirectionsSearchService {
public function search($input = array()) {
$origin = $input['origin'];
$destination = $input['destination'];
$mode = $input['mode'];
// do stuf...
return $data;
}
}
I read the docs and some place else (and this too) and did what I was supposed to do to register a service:
class DirectionsAPIController extends BaseController {
public function search() {
App::register('libraries\Services\Directions\DirectionsSearchService');
$directionsSearchService = new DirectionsSearchService();
return $directionsSearchService->search(Input::all());
}
}
// app/libraries/Services/Directions/DirectionsSearchService.php
use Illuminate\Support\ServiceProvider;
class DirectionsSearchService extends ServiceProvider {
}
I also tried adding libraries\Services\Directions\DirectionsSearchService to the providers array in app/config/app.php.
However, I am getting this error:
HP Fatal error: Class
'libraries\Services\Directions\DirectionsSearchService' not found in
/home/user/www/my-app-laravel/bootstrap/compiled.php on line 549
What am I doing wrong? And what is the usual way to use your own services? I don't want to place all the logic in the controller...
2 main things that you are missing:
There is a difference between a ServiceProvider and your class. A service provider in Laravel tells Laravel where to go look for the service, but it does not contain the service logic itself. So DirectionsSearchService should not be both, imho.
You need to register your classes with composer.json so that autoloader knows that your class exists.
To keep it simple I'll go with Laravel IoC's automatic resolution and not using a service provider for now.
app/libraries/Services/Directions/DirectionsSearchService.php:
namespace Services\Directions;
class DirectionsSearchService
{
public function search($input = array())
{
// Your search logic
}
}
You might notice that DirectionsSearchService does not extend anything. Your service becomes very loosely coupled.
And in your DirectionsAPIController.php you do:
class DirectionsAPIController extends BaseController
{
protected $directionsSearchService;
public function __construct(Services\Directions\DirectionsSearchService $directionsSearchService)
{
$this->directionsSearchService = $directionsSearchService;
}
public function search()
{
return $this->directionsSearchService->search(Input::all());
}
}
With the code above, when Laravel tries to __construct() your controller, it will look for Services\Directions\DirectionsSearchService and injects into the controller for you automatically. In the constructor, we simply need to set it to an instance variable so your search() can use it when needed.
The second thing that you are missing is to register your classes with composer's autoload. Do this by adding to composer.json's autoload section:
"autoload": {
"classmap": [
... // Laravel's default classmap autoloads
],
"psr-4": {
"Services\\": "app/libraries/Services"
}
}
And do a composer dump-autoload after making changes to composer.json. And your code should be working again.
The suggestion above can also be better with a service provider and coding to the interface. It would make it easier to control what to inject into your controller, and hence easier to create and inject in a mock for testing.
It involves quite a few more steps so I won't mention that here, but you can read more in Exploring Laravel’s IoC container and Laravel 4 Controller Testing.

Jackson and REST Android Annotations: Deserialize JSON Array of Objects

I have a REST service which returns data that looks like this:
[
{ bookmark object json here },
{ bookmark object json here },
{ bookmark object json here },
...
]
My REST client class looks like this:
#Rest(rootUrl = Constants.ApiConfig.API_ROOT, converters = {MappingJackson2HttpMessageConverter.class})
public interface RestApiClient {
#Get("/bookmark/read?id={identifier}")
public BookmarkList getBookmarks(String identifier);
}
BookmarkList looks like this:
public class BookmarkList {
List<Bookmark> bookmarks;
#JsonValue
public List<Bookmark> getBookmarks() {
return bookmarks;
}
#JsonCreator
public void BookmarkList(#NotNull List<Bookmark> bookmarks) {
this.bookmarks = bookmarks;
}
}
However, when I utilize this setup, I get the following error:
Could not read JSON: Can not deserialize instance of com.whatever.entity.BookmarkList out of START_ARRAY token
What I want is something like the EventList example at https://github.com/excilys/androidannotations/wiki/Rest-API#get, but that doesn't seem to work for me out of the box.
Is there a way to get this working?
Ho... We have to update this part of documentation. The wrapper solution works but doesn't fit APIs.
If you're looking at the generated code for #Get("url") MyReturnedType testService(), you should see something like this :
return restTemplate.exchange(rootUrl.concat("url"), //
HttpMethod.GET, //
null, //
MyReturnedType.class, //
urlVariables)//
.getBody();
The returned class is injected as a parameter of exchange call. In case of generics collection (like List<MyReturnedType>), we can't inject List.class because of type checking in the return of exchange method.
However, you should be able to use this little trick in your #Rest annotated method :
public class BookmarkList extends List<Bookmark> {
}
I think I misunderstood the example at https://github.com/excilys/androidannotations/wiki/Rest-API#get. I think the array still must be wrapped inside a JSON object in that example (It'd be nice if they included example JSON data).
The data the service I'm connecting to does not return an object wrapping the array like that, so, I altered the REST client to look like this:
#Rest(rootUrl = Constants.ApiConfig.API_ROOT, converters = {MappingJackson2HttpMessageConverter.class})
public interface RestApiClient {
#Get("/bookmark/read?id={identifier}")
public ArrayNode getBookmarks(String identifier);
}
And I wrote a method in another class to iterate the ArrayNode and build the bookmarks:
public List<Bookmark> getBookmarks(Content content) {
ArrayList<Bookmark> bookmarks = new ArrayList<Bookmark>();
ArrayNode bookmarksData = apiClient.getBookmarks(content.getAcid());
for(JsonNode bookmarkData : bookmarksData) {
Bookmark bookmark = objectMapper.convertValue(bookmarkData, Bookmark.class);
bookmarks.add(bookmark);
}
return bookmarks;
}
So it's not as convenient (I had to write more code myself), but I got it working.