Somewhat based on this guide:
https://jaxlondon.com/blog/java-core-languages/put-spring-boot-und-vue-js-practical-use-project-tutorial/
I have created a multi module maven project where one submodule is my backend and another submodule is my frontend. When I build the whole project first the frontend is "build" then its dist/ resources are copied to the backend which is then build and I can successfully start my spring boot backend with java -jar target/backend-1.0.0-SNAPSHOT and access it on localhost:8080
which makes sense based on the controller I have implemented in the backend:
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
#RequestMapping("/")
public Greeting root(#RequestParam(value = "name", defaultValue = "Root!") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
If I instead access: http://localhost:8080/index.html I end up in my frontend:
Which currently have the following two routes:
router.js
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: HomeRoute
},
{
path: '/myroute',
name: 'myroute',
component: MyRoute
}
]
});
export default router;
And in e.g. App.vue I have:
<template>
<div class="hello">
<li>
<router-link to="/MyRoute">GoToMyRoute</router-link>
</li>
<li>
<router-link to="/">GoToHome</router-link>
</li>
<router-view></router-view>
</div>
</template>
That I can also access, e.g.:
So far so good. But if I try to enter:http://localhost:8080/MyRoute directly in my browser I get:
which I assume is because I am missing a backend #RequestMapping for /MyRoute in my controller.
Based on the above my questions become:
Do I need to maintain a backend RequestMapping for each vuejs route I have if I want to be able to access it directly in the browser?
How do I separate/order my frontend and backend endpoint? Right now it seems there is no convention for when a backend endpoint is accessed compared to a pure frontend endpoint/route.
I would suggest you to do it this way:
Have one "ui" controller on your backend which would forward any unmapped routes to your frontend application e.g.:
#RequestMapping(value = "/**/{[path:[^\\.]*}")
public String redirect() {
// Forward to home page so that route is preserved.
return "forward:/";
}
Other rest endpoints defined in your backend should be defined after some prefix like "/api", or "/rest" (e.g. localhost:8080/api/some-data would return your json data). So every data endpoint will have this prefix.
Every route visible to user (view navigation) should be done on vue.js side as SPA routing. (e.g. localhost:8080/welcome etc.)
I tried with the following code (inspired by Gus answer):
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
#RestController
public class RouterCtrl {
#RequestMapping("/**/{path:[^.]*}")
public ModelAndView redirect() {
return new ModelAndView("forward:/");
}
}
And consider:
Your backend endpoints must start with the prefix api/ (or some other distinctive word)
Deal with the 404 not found with Router Vue, something like this:
Vue.use(Router);
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: HomeRoute
},
{
path: '/myroute',
name: 'myroute',
component: MyRoute
},
{
path: '/:catchAll(.*)',
name: 'notFound',
component: NotFound
}
]
});
export default router;
I made a simple gif to ilustrate :9
I would suggest remove the following from your code:
#RequestMapping("/")
public Greeting root(#RequestParam(value = "name", defaultValue = "Root!") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
Once the above is done, all routes will then go via index.html and your vue routes will take over from there.
You can place your other endpoints behind /api or something else.
Related
I am starting to build a Microservice API Gateway, and I am considering Spring Cloud to help me with the routing. But some calls to the Gateway API will need multiple requests to different services.
Lets say I have 2 services: Order Details Service and Delivery Service. I want to have a Gateway endpoint GET /orders/{orderId} that makes a call to Order Details service and then Delivery Service and combine the two to return full Order details with delivery. Is this possible with the routing of Spring cloud or should I make these by hand using something like RestTemplate to make the calls?
There is an enhancement proposal posted on GitHub to have routes support multiple URIs. So far, there aren't any plans to implement this yet, at least, not according to one of the contributors.
As posted in the Spring Cloud Gateway Github issue mentioned by g00glen00b, until the library develops a Filter for this, I resolved it using the ModifyResponseBodyGatewayFilterFactory in my own custom Filter.
Just in case it's useful for anyone else, I provide the base implementation here (it may need some rework, but it should be enough to make the point).
Simply put, I have a "base" service retrieving something like this:
[
{
"targetEntryId": "624a448cbc728123b47d08c4",
"sections": [
{
"title": "sadasa",
"description": "asda"
}
],
"id": "624a448c45459c4d757869f1"
},
{
"targetEntryId": "624a44e5bc728123b47d08c5",
"sections": [
{
"title": "asda",
"description": null
}
],
"id": "624a44e645459c4d757869f2"
}
]
And I want to enrich these entries with the actual targetEntry data (of course, identified by targetEntryId).
So, I created my Filter based on the ModifyResponseBody one:
/**
* <p>
* Filter to compose a response body with associated data from a second API.
* </p>
*
* #author rozagerardo
*/
#Component
public class ComposeFieldApiGatewayFilterFactory extends
AbstractGatewayFilterFactory<ComposeFieldApiGatewayFilterFactory.Config> {
public ComposeFieldApiGatewayFilterFactory() {
super(Config.class);
}
#Autowired
ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter;
ParameterizedTypeReference<List<Map<String, Object>>> jsonType =
new ParameterizedTypeReference<List<Map<String, Object>>>() {
};
#Value("${server.port:9080}")
int aPort;
#Override
public GatewayFilter apply(final Config config) {
return modifyResponseBodyFilter.apply((c) -> {
c.setRewriteFunction(List.class, List.class, (filterExchange, input) -> {
List<Map<String, Object>> castedInput = (List<Map<String, Object>>) input;
// extract base field values (usually ids) and join them in a "," separated string
String baseFieldValues = castedInput.stream()
.map(bodyMap -> (String) bodyMap.get(config.getOriginBaseField()))
.collect(Collectors.joining(","));
// Request to a path managed by the Gateway
WebClient client = WebClient.create();
return client.get()
.uri(UriComponentsBuilder.fromUriString("http://localhost").port(aPort)
.path(config.getTargetGatewayPath())
.queryParam(config.getTargetQueryParam(), baseFieldValues).build().toUri())
.exchangeToMono(response -> response.bodyToMono(jsonType)
.map(targetEntries -> {
// create a Map using the base field values as keys fo easy access
Map<String, Map> targetEntriesMap = targetEntries.stream().collect(
Collectors.toMap(pr -> (String) pr.get("id"), pr -> pr));
// compose the origin body using the requested target entries
return castedInput.stream().map(originEntries -> {
originEntries.put(config.getComposeField(),
targetEntriesMap.get(originEntries.get(config.getOriginBaseField())));
return originEntries;
}).collect(Collectors.toList());
})
);
});
});
}
;
#Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("originBaseField", "targetGatewayPath", "targetQueryParam",
"composeField");
}
/**
* <p>
* Config class to use for AbstractGatewayFilterFactory.
* </p>
*/
public static class Config {
private String originBaseField;
private String targetGatewayPath;
private String targetQueryParam;
private String composeField;
public Config() {
}
// Getters and Setters...
}
}
For completeness, this is the corresponding route setup using my Filter:
spring:
cloud:
gateway:
routes:
# TARGET ENTRIES ROUTES
- id: targetentries_route
uri: ${configs.api.tagetentries.baseURL}
predicates:
- Path=/api/target/entries
- Method=GET
filters:
- RewritePath=/api/target/entries(?<segment>.*), /target-entries-service$\{segment}
# ORIGIN ENTRIES
- id: originentries_route
uri: ${configs.api.originentries.baseURL}
predicates:
- Path=/api/origin/entries**
filters:
- RewritePath=/api/origin/entries(?<segment>.*), /origin-entries-service$\{segment}
- ComposeFieldApi=targetEntryId,/api/target/entries,ids,targetEntry
And with this, my resulting response looks as follows:
[
{
"targetEntryId": "624a448cbc728123b47d08c4",
"sections": [
{
"title": "sadasa",
"description": "asda"
}
],
"id": "624a448c45459c4d757869f1",
"targetEntry": {
"id": "624a448cbc728123b47d08c4",
"targetEntityField": "whatever"
}
},
{
"targetEntryId": "624a44e5bc728123b47d08c5",
"sections": [
{
"title": "asda",
"description": null
}
],
"id": "624a44e645459c4d757869f2",
"targetEntry": {
"id": "624a44e5bc728123b47d08c5",
"targetEntityField": "somethingelse"
}
}
]
I'm developing a Spring boot application using Thymeleaf as the view technology. I have a html page dashboard.html inside src/main/resources/templates folder, which is being called from inside a controller.
#PostMapping("/users/register")
public String registerUser(#Validated #ModelAttribute User user, Model model) {
User registeredUser = usersDAO.registerUser(user);
if (registeredUser == null) {
return "500error";
}
model.addAttribute("name", user.getName());
model.addAttribute("username", user.getUsername());
model.addAttribute("emailId", user.getEmailId());
return "dashboard";
}
I have some more static html files inside static folder. I want to call dashboard.html from a static html file like using anchor tag <a/>. How can this be done?
I cannot directly link to this file when my app is running locally. For example: localhost:8080/templates/dashboard.html will not work.
You should create a controller for your thymeleaf html template. For example:
#Controller
#RequestMapping("/templates")
public class DashboardController {
#GetMapping("/dashboard")
public ModelAndView dashboard() {
DashboardModel dashboardModel = new DashboardModel();
return new ModelAndView("dashboard", "dashboard", dashboardModel);
}
}
Then you can link to http://localhost:8080/templates/dashboard and get your dashboard.html page.
Of course you can change the #RequestMapping("/templates") and #GetMapping("/dashboard") to control the url as you like.
My days of Java web development now lie about 6 years behind me and despite my hectic new life as a non-technical consultant I want to get back in to the tech world and equip myself with some essential web dev skills.
To get me started, I installed a vagrant box using this tutorial:
https://dzone.com/articles/vagrant
... which worked like a treat and got me my Ubuntu box on my Windows host machine up and running in no time. Also it comes with Java 7 and the Tomcat app server that I'm still quite familiar with from past days. Notwithstanding the fact that there are probably better servers out there to practice on, this one works and I'll use it for my tinkering for now. The example web app that came with the tutorial also works, so I'm confident that my Tomcat is running on the guest machine on port 8080.
The next step was to find a good AngularJS and Spring MVC tutorial. Again, while I know that AngularJS is the latest craze in web dev, Spring MVC may be somewhat outdated (?) but since I'm a Java-boy since I hatched from the Uni-egg I wouldn't mind going with it for now.
The tutorial I found is this one:
http://websystique.com/springmvc/spring-mvc-4-angularjs-example/
I downloaded the project from git and deployed it into my tomcat webapps folder. In the user_service.js file I left the REST_SERVICE_URI as http://localhost:8080/Spring4MVCAngularJSExample/user/ given that Tomcat runs on port 8080 on the host Ubuntu box and I can access the application on my guest machine in the browser at http://192.168.33.10:8080/Spring4MVCAngularJSExample
The problem is that the application (while it's showing up in the browser), does not load the mock-users that are populated in the UserServiceImpl class and that should show up when loading the app. When I check my Firefox console under the JavaScript tab, I get the 'Error while fetching Users' error message from the fetchAllUsers function in the user_controller.js script.
I suspect that the problem here is that the front-end (AngularJS $http service) cannot contact the back-end (Spring service). If there were no users in the back-end and returned 0, I wouldn't get the error but an empty set instead, hence my suspicion of some other problem.
My question is how to debug this web app from here? I have tried to look through the front-end console logs using the FF Developer tool (Debugger) and I must admit I haven't written any JUnit test to actually run a test against the Spring service implementation class.
Thanks for your advice, and let me know if I should provide any more details.
Cheers
AHL
Spring controller:
package com.websystique.springmvc.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import com.websystique.springmvc.model.User;
import com.websystique.springmvc.service.UserService;
#RestController
public class HelloWorldRestController {
#Autowired
UserService userService; //Service which will do all data retrieval/manipulation work
//-------------------Retrieve All Users--------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
List<User> users = userService.findAllUsers();
if(users.isEmpty()){
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}
//-------------------Retrieve Single User--------------------------------------------------------
#RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(#PathVariable("id") long id) {
System.out.println("Fetching User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
//-------------------Create a User--------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.POST)
public ResponseEntity<Void> createUser(#RequestBody User user, UriComponentsBuilder ucBuilder) {
System.out.println("Creating User " + user.getUsername());
if (userService.isUserExist(user)) {
System.out.println("A User with name " + user.getUsername() + " already exist");
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
userService.saveUser(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
//------------------- Update a User --------------------------------------------------------
#RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public ResponseEntity<User> updateUser(#PathVariable("id") long id, #RequestBody User user) {
System.out.println("Updating User " + id);
User currentUser = userService.findById(id);
if (currentUser==null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
currentUser.setUsername(user.getUsername());
currentUser.setAddress(user.getAddress());
currentUser.setEmail(user.getEmail());
userService.updateUser(currentUser);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}
//------------------- Delete a User --------------------------------------------------------
#RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteUser(#PathVariable("id") long id) {
System.out.println("Fetching & Deleting User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("Unable to delete. User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
userService.deleteUserById(id);
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}
//------------------- Delete All Users --------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteAllUsers() {
System.out.println("Deleting All Users");
userService.deleteAllUsers();
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}
}
IndexController.java:
package com.websystique.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
#RequestMapping("/")
public class IndexController {
#RequestMapping(method = RequestMethod.GET)
public String getIndexPage() {
return "UserManagement";
}
}
Javascript user_controller.js:
'use strict';
angular.module('myApp').controller('UserController', ['$scope', 'UserService', function($scope, UserService) {
var self = this;
self.user={id:null,username:'',address:'',email:''};
self.users=[];
self.submit = submit;
self.edit = edit;
self.remove = remove;
self.reset = reset;
fetchAllUsers();
function fetchAllUsers(){
UserService.fetchAllUsers()
.then(
function(d) {
self.users = d;
},
function(errResponse){
console.error('Error while fetching Users');
}
);
}
function createUser(user){
UserService.createUser(user)
.then(
fetchAllUsers,
function(errResponse){
console.error('Error while creating User');
}
);
}
function updateUser(user, id){
UserService.updateUser(user, id)
.then(
fetchAllUsers,
function(errResponse){
console.error('Error while updating User');
}
);
}
function deleteUser(id){
UserService.deleteUser(id)
.then(
fetchAllUsers,
function(errResponse){
console.error('Error while deleting User');
}
);
}
function submit() {
if(self.user.id===null){
console.log('Saving New User', self.user);
createUser(self.user);
}else{
updateUser(self.user, self.user.id);
console.log('User updated with id ', self.user.id);
}
reset();
}
function edit(id){
console.log('id to be edited', id);
for(var i = 0; i < self.users.length; i++){
if(self.users[i].id === id) {
self.user = angular.copy(self.users[i]);
break;
}
}
}
function remove(id){
console.log('id to be deleted', id);
if(self.user.id === id) {//clean form if the user to be deleted is shown there.
reset();
}
deleteUser(id);
}
function reset(){
self.user={id:null,username:'',address:'',email:''};
$scope.myForm.$setPristine(); //reset Form
}
}]);
Javascript user_service.js:
'use strict';
angular.module('myApp').factory('UserService', ['$http', '$q', function($http, $q){
var REST_SERVICE_URI = 'http://localhost:8080/Spring4MVCAngularJSExample/user/';
var factory = {
fetchAllUsers: fetchAllUsers,
createUser: createUser,
updateUser:updateUser,
deleteUser:deleteUser
};
return factory;
function fetchAllUsers() {
var deferred = $q.defer();
$http.get(REST_SERVICE_URI)
.then(
function (response) {
deferred.resolve(response.data);
},
function(errResponse){
console.error('Error while fetching Users');
deferred.reject(errResponse);
}
);
return deferred.promise;
}
function createUser(user) {
var deferred = $q.defer();
$http.post(REST_SERVICE_URI, user)
.then(
function (response) {
deferred.resolve(response.data);
},
function(errResponse){
console.error('Error while creating User');
deferred.reject(errResponse);
}
);
return deferred.promise;
}
function updateUser(user, id) {
var deferred = $q.defer();
$http.put(REST_SERVICE_URI+id, user)
.then(
function (response) {
deferred.resolve(response.data);
},
function(errResponse){
console.error('Error while updating User');
deferred.reject(errResponse);
}
);
return deferred.promise;
}
function deleteUser(id) {
var deferred = $q.defer();
$http.delete(REST_SERVICE_URI+id)
.then(
function (response) {
deferred.resolve(response.data);
},
function(errResponse){
console.error('Error while deleting User');
deferred.reject(errResponse);
}
);
return deferred.promise;
}
}]);
On the server (the vagrant host machine), I can wget the URL and get my data back from the Spring server:
vagrant#precise32:~$ wget http://localhost:8080/Spring4MVCAngularJSExample/user/--2016-08-26 11:08:24-- http://localhost:8080/Spring4MVCAngularJSExample/user/
Resolving localhost (localhost)... 127.0.0.1
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/json]
Saving to: `index.html'
[ <=> ] 206 --.-K/s in 0s
2016-08-26 11:08:24 (9.77 MB/s) - `index.html' saved [206]
vagrant#precise32:~$ less index.html
this gives me the expected result set:
[{"id":1,"username":"Sam","address":"NY","email":"sam#abc.com"},{"id":2,"username":"Tomy","address":"ALBAMA","email":"tomy#abc.com"},{"id":3,"username":"Kelly","address":"NEBRASKA","email":"kelly#abc.com"}]
From your spring controller code all your request mappings are expecting user in the url so if you don't have this the spring controllers will not be called. Is you dispatcher servlet set up to except all http requests E.g /
The problem was a simple mistake:
In the user_service.js file, the REST_SERVICE_URI must be set to the address of the host machine:
http://192.168.33.10:8080/Spring4MVCAngularJSExample/user/
So, when deploying this to a (remote) server, I suppose the 192.168.33.10:8080 portion would need to be changed to that server's IP and the respective Tomcat port.
My problem existed (probably) because I was using a virtual box and had (mistakenly) used the host machine's IP instead of the guest machine's IP. Please correct me if I'm wrong, I'm still somewhat confused ....
I am using Struts 2 class which implements ModelDriven. I am able to pass the data from jQuery and save the data on the database.
When I try to retrieve the data back and pass it back to jQuery, I am not sure why it's not available in jQuery. I am sure I am missing something with the basic flow.
Here is my action class:
public HttpHeaders index() {
model = projectService.getProjectDetails(project.getUserID());
return new DefaultHttpHeaders("success").setLocationId("");
}
#Override
public Object getModel() {
return project;
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
Here is my jQuery:
function getProjectDetails() {
var userID = localStorage.getItem('userID');
var request = $.ajax({
url : '/SUH/project.json',
data : {
userID : userID
},
dataType : 'json',
type : 'GET',
async : true
});
request.done(function(data) {
console.log(JSON.stringify(data));
$.each(data, function(index, element) {
console.log('element project--->' + index + ":" + element);
});
});
request.fail(function(jqXHR, textStatus) {
console.log('faik');
});
}
The model object in the Action class has all the data available but I tried to return model or project objects but both didn't work.
By default Struts2 REST plugin is using json-lib for serialization your beans. If you are using ModelDriven then it accesses your model directly when it processes a result. Since you are using the extension .json in the request URL the content type handler is selected by extension. It should be JsonLibHandler.
This handler is using JSONArray.fromObject(obj) if obj is an array or list or JSONObject.fromObject(obj) otherwise to get JSONObejct that could be serialized and written to the response.
The obj is the value returned by getModel(), in your case it will be project.
Because JsonLibHandler is using default JsonConfig you can't exclude properties from the bean to be serialized unless they are public fields.
The following features of json-lib could be powered with JsonConfig:
Cycle detection, there are two default strategies (default throws
an exception), you can register your own
Skip transient fields when
serailizing to JSON (default=don't skip) Skip JAP #Transient annotated
methods when serailizing to JSON (default=don't skip)
Exclude bean
properties and/or map keys when serailizing to JSON
(default=['class','metaClass','declaringClass'])
Filters provide a
finer detail for excluding/including properties when serializing to
JSON or transforming back to Java
You can find this code snippets that allows you to exclude some properties.
Exclude properties
String str = "{'string':'JSON', 'integer': 1, 'double': 2.0, 'boolean': true}";
JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setExcludes( new String[]{ "double", "boolean" } );
JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON( str, jsonConfig );
assertEquals( "JSON", jsonObject.getString("string") );
assertEquals( 1, jsonObject.getInt("integer") );
assertFalse( jsonObject.has("double") );
assertFalse( jsonObject.has("boolean") );
Exclude properties (with filters)
String str = "{'string':'JSON', 'integer': 1, 'double': 2.0, 'boolean': true}";
JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setJsonPropertyFilter( new PropertyFilter(){
public boolean apply( Object source, String name, Object value ) {
if( "double".equals(value) || "boolean".equals(value) ){
return true;
}
return false;
}
});
JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON( str, jsonConfig );
assertEquals( "JSON", jsonObject.getString("string") );
assertEquals( 1, jsonObject.getInt("integer") );
assertFalse( jsonObject.has("double") );
assertFalse( jsonObject.has("boolean") );
But you have an option to use your own ContentTypeHandler to override defaults.
The alternative is to use Jackson library to handle request. As described in the docs page: Use Jackson framework as JSON ContentTypeHandler.
The default JSON Content Handler is build on top of the JSON-lib. If
you prefer to use the Jackson framework for JSON serialisation, you
can configure the JacksonLibHandler as Content Handler for your json
requests.
First you need to add the jackson dependency to your web application
by downloading the jar file and put it under WEB-INF/lib or by adding
following xml snippet to your dependencies section in the pom.xml when
you are using maven as build system.
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.13</version>
</dependency>
Now you can overwrite the Content Handler with the Jackson Content
Handler in the struts.xml:
<bean type="org.apache.struts2.rest.handler.ContentTypeHandler" name="jackson" class="org.apache.struts2.rest.handler.JacksonLibHandler"/>
<constant name="struts.rest.handlerOverride.json" value="jackson"/>
<!-- Set to false if the json content can be returned for any kind of http method -->
<constant name="struts.rest.content.restrictToGET" value="false"/>
<!-- Set encoding to UTF-8, default is ISO-8859-1 -->
<constant name="struts.i18n.encoding" value="UTF-8"/>
After that you can use #JsonIgnore annotation.
I'm trying to call a JavaEE 6 rest service from an Angular factory and I am running into issues.
The java rest service was created by someone else and I'm trying to work off of it and I'm not super-well versed in JavaEE yet, so if you need more info, I'll chip in where I can.
#Path("bills")
public class BillResource {
#EJB
#Resource
private BillableEventBean billableEventBean;
private final BillableEventMapper billableEventMapper = new BillableEventMapper();
BillService implementation not working
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Path("query")
public List<BillableEventDuplicate> listBillableEvents(BillableEventQueryFilter queryFilter) {
List<BillableEvent> ejbRet = billableEventBean.listBillableEvents(queryFilter.getUserID(),
queryFilter.getBillingTeamID(), queryFilter.getBillStatus(), null);
List<BillableEventDuplicate> ret = billableEventMapper.toBillableEventDuplicateList(ejbRet);
return ret;
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("{billableEventI}")
public BillableEventDuplicate getBillableEvent(#PathParam("billableEventI") String id) {
BillableEvent ejbRet = billableEventBean.getBillableEvent(id);
BillableEventDuplicate ret = billableEventMapper.toBillableEventDuplicate(ejbRet);
return ret;
}
}
My angular factory for the service looks like this:
'use strict';
var aumBills = angular.module('appBills', ['ngResource']);
appBills.factory('Bills', ['$resource',
function($resource)
{
return $resource('/ua_appcore/api/v1/bills/:billNumber',
{
billNumber:'#billNumber'
},
{
getList: {method: 'POST', params: {'userID':'ABC123'}, url: '/ua_appcore/api/v1/bills/query/'}
});
}]);
The factory in invoked from the controller thusly:
'use strict';
angular.module('app.bills', ['ngRoute','appBills'])
.config(['$routeProvider', function($routeProvider)
{
$routeProvider.
when('/bills',
{
templateUrl: 'bills/bill-list.html',
controller: 'BillListCtrl'
}).
when('/bills/:billId',
{
templateUrl: 'bills/bill-detail.html',
controller: 'BillDetailCtrl'
}).
when('/billsearch',
{
templateUrl: 'bills/bill-search.html',
controller: 'BillSearchCtrl'
});
}])
.controller('BillListCtrl', ['$scope', '$http', '$routeParams', 'Bills',
function($scope, $http, $routeParams, Bills)
{
Bills.getList({},{}).$promise.then(function(billList)
{
$scope.billList = billList;
});
}])
.controller('BillDetailCtrl', ['$scope', '$http', '$routeParams', 'Bills',
function($scope, $http, $routeParams, Bills)
{
Bills.get({},{billNumber: $routeParams.billNumber }).$promise.then(function(bill)
{
$scope.bill = bill;
});
}]);
In my understanding, I needed to create a custom action in the factory to make use of the URL option since there is a POST to get a list of bills back using the same root call. The problem I'm having is that I can't seem to be able to feed any parameters into the queryFilter object even with the Consumes annotation. Am I doing something fundamentally wrong?
First of all you can use chrome plugin Postman to check if your rest service is working correctly if true there is problem with your angular $resource. In my opinion there is missing header "Content-Type=application/json" in your request. I've never use angular in that way you can try to create service like that (this tutorial should be also helpful)
app.service("billsService", function ($resource) {
var bills = $resource("/ua_appcore/api/v1/bills/query");
this.getBills = function () {
var billsResource = new bills();
billsResource.sampleVar = 'value'; // here you can place your vars
return billsResource.$save();
}
});
So, it turns out that it was an infrastructure issue the entire time. We had been deploying to a Websphere Liberty server, and it does not contain the entire WebProfile that JavaEE 6 needs to recognize the rest services. Their may be some work-arounds for it, but the quickest short-term solution is to run a full Websphere server for deployments.
Thanks for the help!