In this book, we have explored the many facets of Spring Python while examining code samples. So far, all the code we explored was created using CPython, the original implementation of the Python language.
In this day and age, polyglot programming has attracted an ever increasing interest in the developer community. Java developers are looking at other options to increase developer productivity while still retaining access to the Java ecosystem. Python developers are looking at how to run systems on scalable assets such as the Google App Engine.
Spring Python is coded in pure Python, making it easy to run inside Jython. This opens the door to integration, allowing developers to easily mix Java and Python components with the lightweight power of Spring Python.
In this chapter, we will learn how to:
For this chapter, we will explore integrating Java and Python together while building a flight reservation system. The first step towards building a system like this is to show the customer existing flights and allow the customer to filter the data. While many more features are needed to implement a usable system, this feature will be the focus of this demonstration.
We have already explored using the CherryPy web application framework and will use it again to create the front end for this system. For the back end, we will develop a persistence layer using Java components, while wiring everything together using Spring Python's IoC container and run everything from inside Jython. The other key feature that we will explore is how to integrate pure Java with pure Python components using Spring Python and Pyro.
How would you look at building our Python frontend? One popular approach is to build small, testable blocks that are easily composable. From this perspective, the user interface would be the last step.
Another tactic is to focus on the customer's view and develop the screens, followed by filling in the back end. Since our goal is to test drive the features of Spring Python, let's take this more visual approach.
To speed up our application, let's use Spring Python's command-line coily tool, which we explored earlier.
coily
, let's create a booking applicationThis command generates a set of fi les:
File name |
Description |
---|---|
|
Runable CherryPy application. |
|
Spring Python application context, that contains the wiring for our components. |
|
Contains all the web parts, including HTML. |
|
Contains functions that the view accesses to generate dynamic content for the screens. |
This auto-generated application contains pre-wired components, including security, some basic links, and the bootstrapping needed to launch the CherryPy web server engine. It just needs some minor modifi cations before we start coding our features.
app_context.py
, allowing anonymous users access the main parts of site, while subjecting any link underneath /customer/*
to full security and requiring ROLE_CUSTOMER
@Object def filterChainProxy(self): return CP3FilterChainProxy(filterInvocationDefinitionSource = [ ("/images.*", []), ("/html.*", []), ("/accessDenied.*", []), ("/login.*", ["httpSessionContextIntegrationFilter"]), ("/customer/.*", ["httpSessionContextIntegrationFilter", "exceptionTranslationFilter", "authenticationProcessingFilter", "filterSecurityInterceptor"]) ]) @Object def filterSecurityInterceptor(self): filter = FilterSecurityInterceptor() filter.auth_manager = self.authenticationManager() filter.access_decision_mgr = self.accessDecisionManager() filter.sessionStrategy = self.cherrypySessionStrategy() filter.obj_def_source = [ ("/customer/.*", ["ROLE_CUSTOMER"]) ] return filter
def header(): return """ <html> <head> <title>Spring Flight Reservation System</title> </head> <body> """ def footer(): return """ <hr> <table style="width:100%"><tr> <td><A href="/">Home</A></td> </tr></table> </body> """
@cherrypy.expose
(except index, login
, and logout
)We now have a clean slate from which we can start fleshing out the features of our system.
What is the first piece of data that we need for a flight reservation system? Flight listings! Since customers usually need to search based on a date or location, let's fetch some flight data and then build some search features.
@cherrypy.expose flight reservation systemsearch page link, adding on main pagedef index(self): """This is the root page for your booking app. Its default includes links to all other exposed links automatically.""" return header() + """ <H2>Welcome to Spring Flight Reservation System</H2> <P> <ul> <li><a href="flight_listings">Search Flight Listings</a></li> </ul> <P>""" + footer()
We have now created an HTML bulleted list with one entry to Search Flight Listings.
class Flight(object): def __init__(self, flight=None, departure=None, arrival=None): self.flight = flight self.departure = departure self.arrival = arrival
flight_listings
function and expose it using @cherrypy.expose:
@cherrypy.expose def flight_listings(self): page = header() + """ <H2>Search Flight Listings</H2> <table border="1"> <tr><th>Flight</th><th>Departure</th> <th>Arrival</th></tr> """ for f in self.controller.flights(): page += "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % (f.flight, f.departure, f.arrival) page += "</table>" + footer() return page
We haven't created a search box yet. Before we do, let's ask the controller to give us all fl ight data.
from model import * class BookingController(object): def __init__(self): self.data = [] self.data.append(Flight("SpringAir 14", "9:02am Melbourne", "10:45am Nashville")) self.data.append(Flight("Python Airways 28", "10:40am Orlando", "2:15pm San Francisco")) def flights(self): return self.data
There isn't much there, its just a simple Python array containing one Flight instance. But we want to build this up from small, simple parts that we can easily visualize. With all these parts in place, let's look at the web page again.
Now we can see this small bit of sample fl ight data.
@cherrypy.expose flight reservation systemsearch box, creatingdef flight_listings(self, criteria=""): page = header() + """ <H2>Search Flight Listings</H2> <form method="POST" action="/flight_listings"> Search: <input type="text" name="criteria" value="%s" size="10"/> <input type="submit"/> </form> <table border="1"> <tr> <th>Flight</th> <th>Departure</th> <th>Arrival</th> </tr> """ % criteria for f in self.controller.flights(criteria): page += "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % (f.flight, f.departure, f.arrival) page += "</table>" + footer() return page
We added a text input with a button to the top of the screen. The search criterion is now passed into the controller object to do any filtering.
def flights(self, criteria): return [f for f in self.data if criteria.lower() in str(f).lower()]
This grabs the sample data and applies a fi ltering list comprehension. The filter applies lower()
to both the criteria as well as the stringified version of the flight record.
def __str__(self): return "%s %s %s" % (self.flight, self.departure, self.arrival)
With all these changes, we can now do a simple flight search.
What are some other search options you would consider to make this more sophisticated?
So far, we are looking at a couple rows of sample data. A more realistic scenario would be tapping some type of a live system, listing real-time flights. What would you do if the data was stored in a backend database and a service was already coded in Java?
Let's look at how Jython can integrate these two worlds together. Does Jython support all the Python components that we have used so far?
Jython in late 2009 caught up to Python 2.5 and has many useful features. But many 3rd party Python libraries still can't run on Jython despite this significant improvement. Many libraries utilize Python's C-extension capability, but this isn't supported in Jython. There are also platform assumptions involved with things like threads that defer in CPython compared to the JVM which Jython runs on.
Spring Python makes it easy to bridge this gap through its integration with the Pyro remoting library.
Flight
record as well.public class Flight { private long id; private String flight; private String departure; private String arrival; public long getId() { return this.id; } public void setId(long id) { this.id = id; } public String getFlight() { return this.flight; } public void setFlight(String flight) { this.flight = flight; } public String getDeparture() { return this.departure; } public void setDeparture(String departure) { this.departure = departure; } public String getArrival() { return this.arrival; } public void setArrival(String arrival) { this.arrival = arrival; } public String toString() { return flight + " " + departure + " " + arrival; } }
import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; public class FlightDataSystem { private JdbcTemplate jdbcTemplate; public FlightDataSystem(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public List<Flight flights(String criteria) { return jdbcTemplate.query( "SELECT id, flight, departure, arrival " + "FROM flights", new RowMapper<FlightData>() { @Override public FlightData mapRow(final ResultSet rs, int row) throws SQLException { return new Flight() {{ setId(rs.getLong("id")); setFlight(rs.getString("flight")); setDeparture(rs.getString("departure")); setArrival(rs.getString("arrival")); }}; } }); } }
Notice how this Java code has double braces? This is an anonymous subclass of Flight
, with an initializing block of code. It's a convenient way to remove some of the boilerplate of populating a POJO (Plain Old Java Object).
jar
files. This includes:org.springframework.beans
org.springframework.core
org.springframework.jdbc
org.springframework.transaction
commons-dbcp
commons-logging
commons-pool
mysql-connector-java
It is an exercise for the reader to write a script to re-build the Java parts, considering the jar files.
DROP DATABASE IF EXISTS booking; CREATE DATABASE booking; GRANT ALL ON booking.* TO booking@localhost IDENTIFIED BY 'booking'; USE booking; CREATE TABLE flights ( id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, flight VARCHAR(30), departure VARCHAR(30), arrival VARCHAR(30) ); INSERT INTO flights (flight, departure, arrival) values ('SpringAir 14', '9:02am Melbourne', '10:45am Nashville'), INSERT INTO flights (flight, departure, arrival) values ('Python Airways 28', '10:40am Orlando', '2:15pm San Francisco'),
The following screenshot shows this script being run to setup and load data:
import FlightDataSystem from org.apache.commons.dbcp import BasicDataSource source = BasicDataSource() source.driverClassName = "com.mysql.jdbc.Driver" source.url = "jdbc:mysql://localhost/booking" source.username = "booking" source.password = "booking" system = FlightDataSystem(source) for f in system.getFlightData(): print f
To run it, we need to include several jar files.
You can see the command typed, and the output of printing the fl ight data to the console.
There is one other key factor to deal with before we can wire things together: serialization of the data. When our Java code returns an array of Java objects, it has to be serialized by Pyro, sent over the wire, and then deserialized by CPython. For the scalars, this is not a problem. Jython handles it easily. But moving class objects requires class defi nitions on both sides of the wire. CPython doesn't have access to our Java Flight
class and the Java code doesn't have access to the CPython class. We need a thin piece of code in the middle that can call our Java service and convert the list of Java objects into Python objects. A Jython wrapper would be perfect.
from model import * class JythonFlightDataSystem(object): def __init__(self, java_system): self.java_system = java_system def flights(self, criteria): return [Flight(f.flight, f.departure, f.arrival) for f in self.java_system.flights(criteria)]
This wrapper class has the same signature. Running it in Jython will give it access to the Java classes as well as our Python classes.
PyroServiceExporter
import logging from springpython.config import * from springpython.context import * from springpython.remoting.pyro import * import FlightDataSystem from org.apache.commons.dbcp import BasicDataSource from java_wrapper import * class JavaSystemConfiguration(PythonConfig): def __init__(self): super(JavaSystemConfiguration, self).__init__() @Object def data_source(self): source = BasicDataSource() source.driverClassName = "com.mysql.jdbc.Driver" source.url = "jdbc:mysql://localhost/booking" source.username = "booking" source.password = "booking" return source @Object def exported_controller(self): exporter = PyroServiceExporter() exporter.service_name = "JavaFlightDataSystem" exporter.service = self.controller() return exporter @Object def controller(self): java_system = FlightDataSystem(self.data_source()) wrapper = JythonFlightDataSystem(java_system) return wrapper
BasicDataSource
to create a connection pool to access the MySQL database FlightDataSystem
. But instead of returning that, we inject it into our JythonFlightDataSystem
, so that it can translate between Java and Python records PyroServiceExporter
, and have it target our controller()
. It is advertised under the name JavaFlightDataSystem
import logging import os import java_app_context from springpython.context import ApplicationContext if __name__ == '__main__': logger = logging.getLogger("springpython") loggingLevel = logging.DEBUG logger.setLevel(loggingLevel) ch = logging.StreamHandler() ch.setLevel(loggingLevel) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) applicationContext = ApplicationContext( java_app_context.JavaSystemConfiguration()) controller = applicationContext.get_object("controller")
jar
files, the jar
files need to be passed in to the JVM as classpath entriesjython -J-classpath org.springframework.jdbc-3.0.1.RELEASE.jar:org.springframework.core-3.0.1.RELEASE.jar:org.springframework.transaction-3.0.1.RELEASE.jar:org.springframework.beans-3.0.1.RELEASE.jar:commons-logging-1.1.1.jar:commons-dbcp-1.4.jar:commons-pool-1.5.4.jar:mysql-connector-java-5.1.12-bin.jar java_system.py
J
argument feeds parameters to the JVM. This is necessary so that the class loaders can pull classes from the jar
filesWe can see that Pyro has been started and is ready to serve data to any clients.
PyroProxyFactory
@Object def controller(self): java_system = PyroProxyFactory() java_system.service_url = "PYROLOC://localhost:7766/JavaFlightDataSystem" return java_system
from springpython.remoting.pyro import *
booking.py
application using CPython, and look at the screensIf we revisit the flight data page, it looks the same:
However, looking closer at the console output from the Jython script, we see a message indicating our Java code is being called.
The filtering logic has also been moved to the Java service, so we get the response.
18.191.239.235