SAGE Computing Services Customised Oracle Training Workshops and Consulting JDeveloper 11g's REST web services ....A change is as good as a REST Chris Muir Oracle Consultant and Trainer http://one-size-doesnt-fit-all.blogspot.com Oracle ACE Director - Fusion Middleware Agenda • Part I: ABCs of Web Services • Part II: REST Web Services in a Nutshell • Part III: Getting your hands dirty with code Part I: Web Services Photo thanks to Lexnger@ Flickr.com under CC What are Web Services? The W3C defines a "web service" as "a software system designed to support interoperable machine-to-machine interaction over a network.” The traditional “Enterprise” view The Traditional “Enterprise” View Images thanks to Harwan @ IconArchive.com – free for non commercial use The contemporary view The “Contemporary” View Images thanks to Harwan @ IconArchive.com – free for non commercial use Key benefits Key Benefits? • • • • • Share data (near realtime) System interoperability Low cost internet delivery Standardized Loosely coupled Photo thanks to vernhart@ Flickr.com under CC History History? RFC-707 A High Level Framework for Network Based Resource Sharing (Remote Procedure Call) Microsoft DCOM Java RMI CORBA SOAP RPC Style SOAP Document Style REST IETF – Internet Engineering Task Fask – ISOC – Internet Society Choice SOAP vs REST Enterprise Ready Programmer Friendly • • • • • • • • • Only over HTTP Verb first (only 4 commands) Simple Familiar • • • • Overly simplistic security Not 100% ideal for SOA Little standardization No emphasis on contract Supports multiple protocols Contract first Procedural (RPC) based Standardized Maximum security • Ideal for SOA support • Over engineered • Simple solutions become complicated • Complex security • Payload inefficiency Part II – REST Web Services in a Nutshell Part II: REST Web Services in a Nutshell Photo thanks to Caucas' @ Flickr.com under CC What is REST? Q: What is REST? A: It stands for Representational State Transfer Q: Huh? Can you say that in English? A: Let me explain, do you surf the web? Q: Um, yes I do (I thought I was asking the questions)....? A: Then you already know it. Browser/URL Understand basics of HTTP The Request – HTTP Protocol GET /about_sage_computing_services.html HTTP/1.1 Host: www.sagecomputing.com.au User-Agent: Mozilla/5.0 Gecko/20100914 Firefox/3.6.10 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Cookie: __utma=4761137.486424683.1273755779.1285256345.1285459361.13;; If-Modified-Since: Sun, 31 Jan 2010 13:35:33 GMT If-None-Match: "1a690fe-40df-f1645340" Cache-Control: max-age=0 HTTP GET/PUT/POST/DELETE URIs Images thanks to Harwan @ IconArchive.com – free for non commercial use Accept HTTP Response The Response - HTTP Protocol HTTP/1.1 200 OK Date: Sun, 26 Sep 2010 00:02:25 GMT Server: Apache/2.0.55 (Ubuntu) Connection: Keep-Alive Keep-Alive: timeout=15, max=96 Etag: "1a690fe-40df-f1645340“ <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en“> <head> <title>SAGE Computing Services - About us</title </head> .... Etc .... </html> HTTP Error Codes Payload/Body Images thanks to Harwan @ IconArchive.com – free for non commercial use Part II – REST Web Services in a Nutshell REST Web Services in a Nutshell • REST also suits web services • Resources – URIs = intuitive by design – gives users & developers ability to anticipate design • Based on HTTP verbs – get/put/post/delete = simple to understand • Stateless = scalable Photo thanks to blitzmaerker @ Flickr.com under CC Support for REST Web Services Support? JAX-RS Jersey Website Java JAX-RS “Jersey” JAX-RS in detail Java JAX-RS & Jersey • • • • • JAX-RS Specification (API) Introduced Java SE 5.0 Uses a simplified POJO approach Implements both the client and/or server Jersey is Oracle/Sun’s Reference Implementation • Jersey 1.0 – 1.0.3 = JAX-RS 1.0 Spec • Jersey 1.1+ = JAX-RS 1.2 Spec Oracle JDeveloper 11g Oracle’s JDeveloper 11g • Recommended version 11.1.1.3.0+ • Many minor issues in earlier versions • Jersey 1.1.5.1+ supported (latest none beta 1.4) • http://one-size-doesnt-fitall.blogspot.com/2010/10/rest-web-servicesin-oracles-jdeveloper.html Part III – Getting your hands dirty with code Part III: Getting your hands dirty with code Photo thanks to Caucas' @ Flickr.com under CC Database “Departments” query HTML Output/Browser Data is Beautiful Sad iPhone Data 01 <html> 02 <head> 03 <style type="text/css"> 04 table {background-color:#F2F2F5;} 05 td {color:#000000; font-family:Arial; padding:8px; background-color:#F2F2F5;} 06 th {font-family:Arial; font-size:12pt; padding:8px; background-color:#CFE0F1;} 07 </style> 08 </head> 09 <body> 10 <table border="0" cellpadding="0" cellspacing="0"> 11 <tr> 12 <th>DEPARTMENT_ID</th> 13 <th>DEPARTMENT_NAME</th> 14 </tr> 15 <tbody> 16 <tr> <td>10</td> <td>Administration</td> </tr> 17 <tr> <td>20</td> <td>Marketing</td> </tr> 18 <tr> <td>30</td> <td>Purchasing</td> </tr> 19 <tr> <td>40</td> <td>Human Resources</td> </tr> 20 <tr> <td>50</td> <td>Shipping</td> </tr> 21 <tr> <td>60</td> <td>IT</td> </tr> 22 <tr> <td>70</td> <td>Public Relations</td> </tr> 23 <tr> <td>80</td> <td>Sales</td> </tr> 24 <tr> <td>90</td> <td>Executive</td> </tr> 25 <tr> <td>100</td> <td>Finance</td> </tr> 26 </tbody> 27 </table> 28 </body> 29 </html> 30 31 32 HTML page – markup + data 01 <html> 02 <head> 03 <style type="text/css"> 04 table {background-color:#F2F2F5;} 05 td {color:#000000; font-family:Arial; padding:8px; background-color:#F2F2F5;} 06 th {font-family:Arial; font-size:12pt; padding:8px; background-color:#CFE0F1;} 07 </style> 08 </head> 09 <body> 10 <table border="0" cellpadding="0" cellspacing="0"> 11 <tr> 12 <th>DEPARTMENT_ID</th> 13 <th>DEPARTMENT_NAME</th> 14 </tr> 15 <tbody> 16 <tr> <td>10</td> <td>Administration</td> </tr> 17 <tr> <td>20</td> <td>Marketing</td> </tr> 18 <tr> <td>30</td> <td>Purchasing</td> </tr> 19 <tr> <td>40</td> <td>Human Resources</td> </tr> 20 <tr> <td>50</td> <td>Shipping</td> </tr> 21 <tr> <td>60</td> <td>IT</td> </tr> 22 <tr> <td>70</td> <td>Public Relations</td> </tr> 23 <tr> <td>80</td> <td>Sales</td> </tr> 24 <tr> <td>90</td> <td>Executive</td> </tr> 25 <tr> <td>100</td> <td>Finance</td> </tr> 26 </tbody> 27 </table> 28 </body> 29 </html> 30 31 32 HTML page - makrup 01 <html> 02 <head> 03 <style type="text/css"> 04 table {background-color:#F2F2F5;} 05 td {color:#000000; font-family:Arial; padding:8px; background-color:#F2F2F5;} 06 th {font-family:Arial; font-size:12pt; padding:8px; background-color:#CFE0F1;} 07 </style> 08 </head> 09 <body> 10 <table border="0" cellpadding="0" cellspacing="0"> 11 <tr> 12 <th>DEPARTMENT_ID</th> 13 <th>DEPARTMENT_NAME</th> 14 </tr> 15 <tbody> 16 <tr> <td>10</td> <td>Administration</td> </tr> 17 <tr> <td>20</td> <td>Marketing</td> </tr> 18 <tr> <td>30</td> <td>Purchasing</td> </tr> 19 <tr> <td>40</td> <td>Human Resources</td> </tr> 20 <tr> <td>50</td> <td>Shipping</td> </tr> 21 <tr> <td>60</td> <td>IT</td> </tr> 22 <tr> <td>70</td> <td>Public Relations</td> </tr> 23 <tr> <td>80</td> <td>Sales</td> </tr> 24 <tr> <td>90</td> <td>Executive</td> </tr> 25 <tr> <td>100</td> <td>Finance</td> </tr> 26 </tbody> 27 </table> 28 </body> 29 </html> 30 31 32 Data – 1 per line 10 Administration 20 Marketing 30 Purchasing 40 Human Resources 50 Shipping 60 IT 70 Public Relations 80 Sales 90 Executive 100 Finance Data – comma delimiited DEPARTMENT_ID,DEPARTMENT_NAME 10,Administration 20,Marketing 30,Purchasing 40,Human Resources 50,Shipping 60,IT 70,Public Relations 80,Sales 90,Executive 100,Finance Data - XML <DEPARTMENTS> <DEPT><DEPTID>10</DEPTID><NAME>Administration</NAME></DEPT> <DEPT><DEPTID>20</DEPTID><NAME>Marketing</NAME></DEPT> <DEPT><DEPTID>30</DEPTID><NAME>Purchasing</NAME></DEPT> <DEPT><DEPTID>40</DEPTID><NAME>Human Resources</NAME></DEPT> <DEPT><DEPTID>50</DEPTID><NAME>Shipping</NAME></DEPT> <DEPT><DEPTID>60</DEPTID><NAME>IT</NAME></DEPT> <DEPT><DEPTID>70</DEPTID><NAME>Public Relations</NAME></DEPT> <DEPT><DEPTID>80</DEPTID><NAME>Sales</NAME></DEPT> <DEPT><DEPTID>90</DEPTID><NAME>Executive</NAME></DEPT> <DEPT><DEPTID>100</DEPTID><NAME>Finance</NAME></DEPT> </DEPARTMENTS> Building a REST Web Service Building a REST Web Service And now for a bit of Java And now for a bit of Java JDBC – Java API for Database Connectivity 01 public class DepartmentsResource { 02 03 public static String queryDepartmentsSQL() throws SQLException { Connection jdbcConn = 04 DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); 05 06 String query = "SELECT department_id, department_name FROM departments"; 07 Statement statement = jdbcConn.createStatement(); 08 ResultSet resultSet = statement.executeQuery(query); 09 10 String result = ""; 11 while (resultSet.next()) { 12 result = result + resultSet.getInt(1) + "\n"; 13 result = result + resultSet.getString(2) + "\n"; 14 } 15 statement.close(); 16 jdbcConn.close(); 17 18 return result; 19 20 } 21 22 public static void main(String[] args) throws SQLException { String result = DepartmentsResource.queryDepartmentsSQL(); 23 System.out.println(result); 24 25 } 26 } RESTDemo3/Project1 RESTDemo3/Project1 01 @Path("/Departments") 02 public class DepartmentsResource { 03 04 public static String queryDepartmentsSQL() throws SQLException { 05 Connection jdbcConn = 06 DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); 07 08 String query = "SELECT department_id, department_name FROM departments"; 09 Statement statement = jdbcConn.createStatement(); 10 ResultSet resultSet = statement.executeQuery(query); 11 12 String result = ""; 13 while (resultSet.next()) { 14 result = result + resultSet.getInt(1) + "\n"; 15 result = result + resultSet.getString(2) + "\n"; 16 } 17 statement.close(); 18 jdbcConn.close(); 19 20 return result; 21 } 22 23 @GET 24 @Produces("plain/text") 25 public String getDepartments() throws SQLException { 26 return DepartmentsResource.queryDepartmentsSQL(); 27 } 28 } RESTDemo3/Project2 You are now REST certified Make solution more Object Oriented POJO And now for some more Java A Plain Old Java Object POJO code 01 public class Department { 02 03 private int deptId; 04 private String name; 05 06 public Department(int newDeptId, String newName) { 07 deptId = newDeptId; 08 name = newName; 09 } 10 11 public void setDeptId(int newDeptId) { 12 deptId = newDeptId; 13 } 14 15 public int getDeptId() { 16 return deptId; 17 } 18 19 public void setName(String newName) { 20 name = newName; 21 } 22 23 public String getName() { 24 return name; 25 } 26 } RESTDemo3/Project3 01 @Path("/Departments") 02 public class DepartmentsResource { 03 04 public static ArrayList<Department> queryDepartmentsSQL() throws SQLException { 05 Connection jdbcConn = 06 DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); 07 08 String query = "SELECT department_id, department_name FROM departments"; 09 Statement statement = jdbcConn.createStatement(); 10 ResultSet resultSet = statement.executeQuery(query); 11 12 ArrayList<Department> departments = new ArrayList<Department>(); 13 while (resultSet.next()) { 14 Department dept = new Department(resultSet.getInt(1), resultSet.getString(2)); 15 departments.add(dept); 16 } 17 statement.close(); 18 jdbcConn.close(); 19 20 return departments; 21 } 22 23 @GET 24 @Produces("plain/text") 25 public String getDepartments() throws SQLException { 26 String result = ""; 27 for (Department department : DepartmentsResource.queryDepartmentsSQL()) { 28 result = result + department.getDeptId() + "," + department.getName() + "\n"; 29 } 30 return result; 31 } 32 } RESTDemo3/Project3 01 @Path("/Departments") 02 public class DepartmentsResource { 03 04 public static Department queryDepartmentSQL(int deptId) throws SQLException { 05 Connection jdbcConn = 06 DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); 07 08 String query = "SELECT department_id, department_name FROM departments " 09 + "WHERE department_id = ?"; 10 PreparedStatement statement = jdbcConn.prepareStatement(query); 11 statement.setInt(1, deptId); 12 ResultSet resultSet = statement.executeQuery(); 13 14 Department department = null; 15 if (resultSet.next()) 16 department = new Department(resultSet.getInt(1), resultSet.getString(2)); 17 18 statement.close(); 19 jdbcConn.close(); 20 21 return department; 22 } 23 24 @GET 25 @Path("/{dept_id}") // /Departments/{dept_id} 26 @Produces("plain/text") 27 public String getDepartment(@PathParam("dept_id") int deptId) throws SQLException 28 Department department = DepartmentsResource.queryDepartmentSQL(deptId); 29 30 return department.getDeptId() + "," + department.getName() + "\n"; 31 } 32 } RESTDemo3/Project4 24 25 26 27 28 29 30 31 32 33 34 @GET @Path("/{dept_id}") @Produces("plain/text") public String getDepartment(@PathParam("dept_id") int deptId) throws SQLException Department department = DepartmentsResource.queryDepartmentSQL(deptId); if (department == null) throw new NotFoundException("Department ID " + deptId + " not found"); return department.getDeptId() + "," + department.getName() + "\n"; } RESTDemo3/Project5 XML? <DEPARTMENTS> <DEPT><DEPTID>10</DEPTID><NAME>Administration</NAME></DEPT> <DEPT><DEPTID>20</DEPTID><NAME>Marketing</NAME></DEPT> <DEPT><DEPTID>30</DEPTID><NAME>Purchasing</NAME></DEPT> <DEPT><DEPTID>40</DEPTID><NAME>Human Resources</NAME></DEPT> <DEPT><DEPTID>50</DEPTID><NAME>Shipping</NAME></DEPT> <DEPT><DEPTID>60</DEPTID><NAME>IT</NAME></DEPT> <DEPT><DEPTID>70</DEPTID><NAME>Public Relations</NAME></DEPT> <DEPT><DEPTID>80</DEPTID><NAME>Sales</NAME></DEPT> <DEPT><DEPTID>90</DEPTID><NAME>Executive</NAME></DEPT> <DEPT><DEPTID>100</DEPTID><NAME>Finance</NAME></DEPT> </DEPARTMENTS> 01 import javax.xml.bind.annotation.*; 02 03 @XmlAccessorType(XmlAccessType.FIELD) 04 @XmlType(name = "", propOrder = { "deptId", "name" }) 05 @XmlRootElement(name = "DEPT") 06 public class Department { 07 08 @XmlElement(name = "DEPTID", required = true) 09 private int deptId; 10 @XmlElement(name = "NAME", required = true) 11 private String name; 12 13 public Department() { } // default no-arg constructor required for JAXB 14 15 public Department(int newDeptId, String newName) { 16 deptId = newDeptId; 17 name = newName; 18 } 19 20 public void setDeptId(int newDeptId) { deptId = newDeptId; } 21 22 public int getDeptId() { return deptId; } 23 24 public void setName(String newName) { name = newName; } 25 26 public String getName() { return name; } 27 } 28 29 30 31 32 RESTDemo3/Project6 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @GET // @Path("/Departments") @Produces("plain/text") public String getDepartments() throws SQLException { String result = ""; for (Department department : DepartmentResources.queryDepartmentsSQL()) { result = result + department.getDeptId() + "," + department.getName() + "\n"; } return result; } @GET @Path("/{dept_id}") // /Departments/{dept_id} @Produces("plain/text") public String getDepartment(@PathParam("dept_id") int deptId) throws SQLException { Department department = DepartmentResources.queryDepartmentSQL(deptId); if (department == null) throw new NotFoundException("Department ID " + deptId + " not found"); return department.getDeptId() + "," + department.getName() + "\n"; } @GET @Path("/{dept_id}") // /Departments/{dept_id} @Produces("application/xml") public Department getDepartmentXML(@PathParam("dept_id") int deptId) throws SQLExce Department department = DepartmentResources.queryDepartmentSQL(deptId); if (department == null) throw new NotFoundException("Department ID " + deptId + " not found"); return department; } RESTDemo3/Project6 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @GET // @Path("/Departments") @Produces("plain/text") public String getDepartments() throws SQLException { String result = ""; for (Department department : DepartmentsResource.queryDepartmentsSQL()) { result = result + department.getDeptId() + "," + department.getName() + "\n"; } return result; } @GET @Path("/{dept_id}") // /Departments/{dept_id} @Produces("plain/text") public String getDepartment(@PathParam("dept_id") int deptId) throws SQLException { Department department = DepartmentsResource.queryDepartmentSQL(deptId); if (department == null) throw new NotFoundException("Department ID " + deptId + " not found"); return department.getDeptId() + "," + department.getName() + "\n"; } @GET @Path("/{dept_id}") // /Departments/{dept_id} @Produces("application/xml") public Department getDepartmentXML(@PathParam("dept_id") int deptId) throws SQLExce Department department = DepartmentsResource.queryDepartmentSQL(deptId); if (department == null) throw new NotFoundException("Department ID " + deptId + " not found"); return department; } RESTDemo3/Project6 <DEPARTMENTS> <DEPT><DEPTID>10</DEPTID><NAME>Administration</NAME></DEPT> <DEPT><DEPTID>20</DEPTID><NAME>Marketing</NAME></DEPT> <DEPT><DEPTID>30</DEPTID><NAME>Purchasing</NAME></DEPT> <DEPT><DEPTID>40</DEPTID><NAME>Human Resources</NAME></DEPT> <DEPT><DEPTID>50</DEPTID><NAME>Shipping</NAME></DEPT> <DEPT><DEPTID>60</DEPTID><NAME>IT</NAME></DEPT> <DEPT><DEPTID>70</DEPTID><NAME>Public Relations</NAME></DEPT> <DEPT><DEPTID>80</DEPTID><NAME>Sales</NAME></DEPT> <DEPT><DEPTID>90</DEPTID><NAME>Executive</NAME></DEPT> <DEPT><DEPTID>100</DEPTID><NAME>Finance</NAME></DEPT> </DEPARTMENTS> 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "departments" }) @XmlRootElement(name = "DEPARTMENTS") public class Departments { @XmlElement(name = "DEPT", required = true) protected List<Department> departments; public List<Department> getDepartments() { if (departments == null) { departments = new ArrayList<Department>(); } return this.departments; } } RESTDemo3/Project7 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static Departments queryDepartmentsSQL() throws SQLException { Connection jdbcConn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:home","hr","hr"); String query = "SELECT department_id, department_name FROM departments"; Statement statement = jdbcConn.createStatement(); ResultSet resultSet = statement.executeQuery(query); Departments departments = new Departments(); List<Department> departmentList = departments.getDepartments(); while (resultSet.next()) { Department dept = new Department(resultSet.getInt(1), resultSet.getString(2)); departmentList.add(dept); } statement.close(); jdbcConn.close(); return departments; } @GET @Produces("application/xml") public Departments getDepartmentsXML() throws SQLException { return DepartmentsResource.queryDepartmentsSQL(); } RESTDemo3/Project7 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static void insertDepartmentSQL(int deptId, String name) throws SQLException { Connection jdbcConn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); String insert = "INSERT INTO departments (department_id, department_name) " + "VALUES (?, ?)"; PreparedStatement statement = jdbcConn.prepareStatement(insert); statement.setInt(1, deptId); statement.setString(2, name); boolean result = statement.execute(); jdbcConn.commit(); statement.close(); jdbcConn.close(); } @POST @Path("/{dept_id}") public void insertDepartment(@PathParam("dept_id") int deptId ,@FormParam("name") String name) throws SQLException { DepartmentsResource.insertDepartmentSQL(deptId, name); } RESTDemo3/Project8 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static void updateDepartmentSQL(int deptId, String name) throws SQLException { Connection jdbcConn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); String update = "UPDATE departments SET department_name = ? " + "WHERE department_id = ?"; PreparedStatement statement = jdbcConn.prepareStatement(update); statement.setString(1, name); statement.setInt(2, deptId); boolean result = statement.execute(); jdbcConn.commit(); statement.close(); jdbcConn.close(); } @PUT @Path("/{dept_id}") public void updateDepartment(@PathParam("dept_id") int deptId ,@FormParam("name") String name) throws SQLException { DepartmentsResource.updateDepartmentSQL(deptId, name); } RESTDemo3/Project8 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void deleteDepartmentSQL(int deptId) throws SQLException { Connection jdbcConn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","hr"); String delete = "DELETE departments WHERE department_id = ?"; PreparedStatement statement = jdbcConn.prepareStatement(delete); statement.setInt(1, deptId); boolean result = statement.execute(); jdbcConn.commit(); statement.close(); jdbcConn.close(); } @DELETE @Path("/{dept_id}") public void deleteDepartment(@PathParam("dept_id") int deptId) throws SQLException { DepartmentsResource.deleteDepartmentSQL(deptId); } RESTDemo3/Project8 (Part IV The stuff I didn’t cover) • • • • • HATEOAS Security JSON WADL Designing REST web services "Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius -- and a lot of courage -- to move in the opposite direction.“ -- Albert Einstein SAGE Computing Services Customised Oracle Training Workshops and Consulting Questions and Answers? Presentations are available from our website: www.sagecomputing.com.au enquiries@sagecomputing.com.au chris.muir@sagecomputing.com.au http://one-size-doesnt-fit-all.blogspot.com Twitter: chriscmuir Linkedin: http://au.linkedin.com/in/chriscmuir