It opens up a nice mobile browser simulator like this, you can use it to simulate multiple devices, use different Cordova functions.
It opens up a nice mobile browser simulator like this, you can use it to simulate multiple devices, use different Cordova functions.
When you right click on your application and say "Build All and Deploy" it actually just copies the application in WorklightServerHome\<appname> directory. In that directory you can see few Jetty server related directories, there is also a log directory which has Jetty server related logs
You can take a look at <workspace>\WorklightServerHome\<appname>\log\server.log for more details on the problems on server.
$.post() method, But jQuery also provides one wrapper method load() that makes making AJAX call even simpler. I wanted to try that so i changed the sample to use the load().
The load() method works on jQuery selector, so it first makes AJAX request and then takes the response of AJAX method and inserts it in all the elements returns by the selector. In my case first i did create a HTTP form and when i click on it, i want to collect all the values entered by the user on the form and use them to make HTTP POST call and once the result it returned i want to insert it into html div with id equal to result. This single line of code does everything
$('div#result').load('http://192.168.1.101:9000/ManageContact/rest/contact',
$("#insertContact :input"));
First i am selecting a div with id equal to result by using $('div#result') selector, the http://192.168.1.101:9000/ManageContact/rest/contact parameter is the URL to which the XHR request should be made. Second paramater $("#insertContact :input") says that select the form insertContact and return all the inputs on it in array format and submit them to the URL.
<!DOCTYPE html>
<html>
<head>
<title>Manage Contact</title>
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$("#submit").click(insertContact);
function insertContact(){
console.log("Entering insertContact()");
$('div#result').load('http://192.168.1.101:9000/ManageContact/rest/contact',
$("#insertContact :input"));
return false;
}
});
</script>
</head>
<body>
<form id="insertContact">
<table>
<tr>
<td>Contact Id</td>
<td><input type="text" name="contactId" /></td>
</tr>
<tr>
<td>First Name</td>
<td><input type="text" name="firstName" /></td>
</tr>
<tr>
<td>Last Name</td>
<td><input type="text" name="lastName" /></td>
</tr>
<tr>
<td>Email</td>
<td><input type="text" name="email" /></td>
</tr>
<tr>
<td><input type="submit" id="submit" name="submit"
value="Submit" /></td>
</tr>
</table>
</form>
<div id='result'></div>
</body>
</html>
The load() method looks at the second parameter to figure out if the request should be made using HTTP GET or POST. If i wanted to submit the form using HTTP GET i should have used
$('div#result').load('http://192.168.1.101:9000/ManageContact/rest/contact',
$("#insertContact").serialize());
data-add-back-btn attribute at page level, I did that and this is screen show of how my page looks
This is the index.html file that i used for my application.
<!DOCTYPE html>
<html>
<head>
<title>Device Properties Example</title>
<script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>
<link rel="stylesheet"
href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
<script type="text/javascript" charset="utf-8">
</script>
</head>
<body>
<div data-role="page" id="page1" data-add-back-btn="true"
data-back-btn-text="Previous" data-title="First Page">
<div data-theme="a" data-role="header">
<h3>First Page</h3>
</div>
<div data-role="content">
<a href="#page2">Go to page 2</a>
</div>
<div data-theme="a" data-role="footer">
<h3>Copyright stuff</h3>
</div>
</div>
<div data-role="page" id="page2" data-add-back-btn="true"
data-back-btn-text="Previous" data-title="Second Page">
<div data-theme="a" data-role="header">
<h3>Second Page</h3>
</div>
<div data-role="content">
<a href="#page3">Go to page 3</a>
</div>
<div data-theme="a" data-role="footer">
<h3>Copyright stuff</h3>
</div>
</div>
<div data-role="page" id="page3" data-add-back-btn="true"
data-back-btn-text="Previous" data-title="Third Page">
<div data-theme="a" data-role="header">
<h3>Third page</h3>
</div>
<div data-role="content">
<h3>Hello from page 3</h3>
</div>
<div data-theme="a" data-role="footer">
<h3>Copyright stuff</h3>
</div>
</div>
</body>
</html>
My index.html page has 3 JQM pages inside it, each one of them has 2 attributes first is data-add-back-btn which says that you want Jquery Mobile to manage back button when their is page in the history, you enable it by setting its value to true.
The data-back-btn-text attribute is used to set title of the back button, in my case i am setting it to Previous
<!DOCTYPE html>
<html>
<head>
<title>Manage Contact</title>
<script type="text/javascript"
charset="utf-8" src="cordova-1.7.0.js"></script>
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$("#submit").click(insertContact);
});
function insertContact(){
console.log("Entering insertContact()");
$.post("http://192.168.1.101:9000/ManageContact/rest/contact",
$("#insertContact :input").serializeArray(),
function(json){
if(json== null || json == 'undefined')
alert("Insert failed");
else
alert("Insert successful");
});
return false;
}
</script>
</head>
<body>
<h3>Insert Contact</h3>
<form id="insertContact">
<table>
<tr>
<td>Contact Id</td>
<td><input type="text" name="contactId" /></td>
</tr>
<tr>
<td>First Name</td>
<td><input type="text" name="firstName" /></td>
</tr>
<tr>
<td>Last Name</td>
<td><input type="text" name="lastName" /></td>
</tr>
<tr>
<td>Email</td>
<td><input type="text" name="email" /></td>
</tr>
<tr>
<td><input type="submit"
id="submit" name="submit" value="Submit" /></td>
</tr>
</table>
</form>
</body>
</html>
When the user clicks on submit button control goes to insertContact() method., In this method i am using jQuery $.post() call to submit the form to http://192.168.1.101:9000/ManageContact/rest/contact URL. I am using jQuery to collect all the values entered by the user into form and encode them by calling $("#insertContact :input").serializeArray() method.
After the post request control goes to the anonymous function which is third parameter of the $.post() method. In that method i am checking if i got response if yes that means insert was successful if not that means insert failed, that is because my REST service is structured not to send anything back in case of insert failure.
http://localhost:9000/ManageContact/rest/contact rest service, read the contact list returned in XML format, parse it and display contact list to the user, you can download the sample application from here
This screen shot displays the list of contacts that i got from the REST service
I followed these steps to build the CordovaManageContact application
<!DOCTYPE html>
<html>
<head>
<title>Device Properties Example</title>
<script type="text/javascript" charset="utf-8"
src="cordova-1.7.0.js"></script>
<script src="js/jquery.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$("#getContactBtn").click(getContactList);
});
function onDeviceReady() {
console.log("Entering index.html.onDeviceReady");
getContactList();
console.log("Exiting index.html.onDeviceReady");
}
function getContactList(){
console.log("Entering getContactList()");
$.ajax({
url : "http://192.168.1.101:9000/ManageContact/rest/contact",
dataType:"xml",
cache: false,
error:function (xhr, ajaxOptions, thrownError){
debugger;
alert(xhr.statusText);
alert(thrownError);
},
success : function(xml) {
console.log("Entering getContactList.success()");
$(xml).find("contact").each(function() {
var html = '<li>' + $(this).find("firstName").text()
+ ' ' + $(this).find("lastName").text() +'</li>';
$('#contactList').append(html);
});
console.log("Exiting getContactList.success()");
}
});
console.log("Exiting getContactList()");
}
</script>
</head>
<body>
<h3>Contact List</h3>
<button id="getContactBtn">Get Contact</button>
<ul id="contactList"></ul>
</body>
</html>
When i click on the Get Contact button the getContactList() method gets called, it uses the jquery $.ajax() method to make a call and once the result is returned, it uses logic in the success method to parse the xml and get all the contact records and adds each one of them as list item in the contactList list.
I have the REST service running on my machine along with the Android emulator which has the PhoneGap application but when i tried to access the service at http://localhost/ManageContact/rest/contact,http://127.0.0.1/ManageContact/rest/contact it did not work. I tried to map the ip address 192.168.1.101 to demohost.com in my host file but Android did not understand that mapping either. I had to use ipconfig command to figure out the ip address of the machine and then use it.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.webspherenotes.rest.ContactApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
The com.sun.jersey.api.container.filter.LoggingFilter can be used to enable loggin on either request or response or both as i am doing in this case.
Now if i hit a REST service by making GET call to http://localhost:9000/ManageContact/rest/contact/5 then the service returns JSON reso
If you look into the generated log you can see both the request and response being printed like this
javax.annotation.security annotations. You can download the sample application from here
When the user tries to insert a new record he will get prompted for basic authentication like this
I followed these steps to build the sample application
<build>
<finalName>JettySecurity</finalName>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.4.5.v20110725</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/JettySecurity</contextPath>
</webAppConfig>
<loginServices>
<loginService implementation="org.eclipse.jetty.security.HashLoginService">
<name>Default</name>
<config>${basedir}/src/main/resources/realm.properties</config>
</loginService>
</loginServices>
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9000</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
</plugins>
</build>
${basedir}/src/main/resources directory which looks like this
guest:guest
admin:admin,ADMIN
This file has only 2 users first is guest and second is admin the admin user has ADMIN role.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.webspherenotes.rest.ContactApplication</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>
com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Default</realm-name>
</login-config>
<security-role>
<role-name>ADMIN</role-name>
</security-role>
</web-app>
By default the Jersey implementation does not look for security annotations in your REST service, in order for that to work you must set value of com.sun.jersey.spi.container.ResourceFilters init parameter to com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory this filter takes care of parsing and understanding PermitAll, RolesAllowed and DenyAll annotations
package com.webspherenotes.rest;
import java.util.List;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
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.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Path("/contact")
public class ContactService {
Logger logger = LoggerFactory.getLogger(ContactService.class);
EntityManagerFactory entityManagerFactory;
public ContactService(EntityManagerFactory entityManagerFactory){
this.entityManagerFactory=entityManagerFactory;
}
@GET
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public List getContactList() {
logger.debug("Entering ContactService.getContactList()");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Query q = entityManager.createQuery("SELECT x from Contact x");
logger.debug("Exiting ContactService.getContactList()");
return (List) q.getResultList();
}
@GET
@Path("/{contactId}")
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Contact getContact(@PathParam("contactId") int contactId) {
logger.debug("Entering ContactService.getContact() contactId" + contactId);
EntityManager entityManager = entityManagerFactory.createEntityManager();
Contact contact = entityManager.find(Contact.class, contactId);
logger.debug("Exiting ContactService.getContact()" );
return contact;
}
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
@RolesAllowed("ADMIN")
public Contact insertContact(@FormParam("contactId") int contactId,
@FormParam("firstName") String firstName,
@FormParam("lastName") String lastName,
@FormParam("email") String email) {
logger.debug("Entering ContactService.insertContact()");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Contact contact = new Contact();
contact.setContactId(contactId);
contact.setFirstName(firstName);
contact.setLastName(lastName);
contact.setEmail(email);
try{
entityManager.getTransaction().begin();
entityManager.persist(contact);
entityManager.getTransaction().commit();
}catch(Throwable t){
if(entityManager.getTransaction().isActive())
entityManager.getTransaction().rollback();
contact = null;
}finally{
entityManager.close();
}
logger.debug("Exiting ContactService.insertContact()");
return contact;
}
@PUT
@Path("/{contactId}")
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@RolesAllowed("ADMIN")
public Contact updateContact(@PathParam("contactId") int contactId,
@FormParam("firstName") String firstName,
@FormParam("lastName") String lastName,
@FormParam("email") String email) {
logger.debug("Entering ContactService.update() contactId" + contactId);
EntityManager entityManager = entityManagerFactory.createEntityManager();
Contact contact = new Contact();
contact.setContactId(contactId);
contact.setFirstName(firstName);
contact.setLastName(lastName);
contact.setEmail(email);
try{
entityManager.getTransaction().begin();
entityManager.merge(contact);
entityManager.getTransaction().commit();
}catch(Throwable t){
if(entityManager.getTransaction().isActive())
entityManager.getTransaction().rollback();
contact = null;
}finally{
entityManager.close();
}
logger.debug("Exiting ContactService.updateContact()");
return contact;
}
@DELETE
@Path("/{contactId}")
@RolesAllowed("ADMIN")
public void deleteContact(@PathParam("contactId") int contactId) {
logger.debug("Entering ContactService.deleteContact() contactId " + contactId);
EntityManager entityManager = entityManagerFactory.createEntityManager();
try{
entityManager.getTransaction().begin();
Contact contact = entityManager.find(Contact.class, contactId);
logger.debug("remove contact " + contact);
entityManager.remove(contact);
logger.debug("After removing " + contact);
entityManager.getTransaction().commit();
}catch(Throwable t){
if(entityManager.getTransaction().isActive())
entityManager.getTransaction().rollback();
}finally{
entityManager.close();
}
logger.debug("Exiting ContactService.deleteContact()");
}
}
By default all the methods are accessible to user. But i did add @RolesAllowed("ADMIN") to insertContact(), updateContact() and deleteContact() method to say that only users who have admin rights can access these methods.
I followed these steps to build the sample application
<build>
<finalName>JettySecurity</finalName>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.4.5.v20110725</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/JettySecurity</contextPath>
</webAppConfig>
<loginServices>
<loginService implementation="org.eclipse.jetty.security.HashLoginService">
<name>Default</name>
<config>${basedir}/src/main/resources/realm.properties</config>
</loginService>
</loginServices>
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>9000</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
</plugins>
</build>
${basedir}/src/main/resources directory which looks like this
guest:guest
admin:admin,ADMIN
This file has only 2 users first is guest and second is admin the admin user has ADMIN role.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>
com.webspherenotes.rest.ContactApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Create Contact</web-resource-name>
<url-pattern>/rest/*</url-pattern>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Default</realm-name>
</login-config>
<security-role>
<role-name>ADMIN</role-name>
</security-role>
</web-app>
The most important change in web.xml is defining the security constraints for the /rest URL, which is the base URL for the REST service. The security constraint says that only allow those users who have ADMIN role access to POST, PUT, DELETE HTTP methods on this URL. The login-config element says that use Basic authentication mvn jetty:run and you will notice that you can get list of contacts but when you try to either insert a new contact or delete existing contact then you will get prompted for userid and password