Chapter 10. Case Study II—Integrating Spring Python with your Java Application

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:

  • Build a flight reservation system front end using the pythonic CherryPy web application framework
  • Build a flight reservation system back end using Java
  • Connect the two together using Spring Python, Jython, and Pyro

Building a flight reservation system

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.

Building a web app the fastest way

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.

  1. Using coily, let's create a booking application
Building a web app the fastest way

This command generates a set of fi les:

File name

Description

booking.py

Runable CherryPy application.

app_context.py

Spring Python application context, that contains the wiring for our components.

view.py

Contains all the web parts, including HTML.

controller.py

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.

  1. Remove any licensing comments embedded in the app. While the template files themselves are marked with a license, this application may not be released under the same license
  2. Ease security restrictions defined in 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
    
  3. Remove the images and CSS code and replace them with a simpler header and footer:
    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>
    """
    
  4. Remove all methods from the view layers that were exposed using @cherrypy.expose (except index, login, and logout)
  5. Let's launch this web app using Python and then visit it by opening a browser at http://localhost:8080
Building a web app the fastest way
Building a web app the fastest way

We now have a clean slate from which we can start fleshing out the features of our system.

Looking up existing flights

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.

  1. Let's add a link on the main page to take us to a search page.
    @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.

    Looking up existing flights
  2. Before we try to display any flight information, let's define a simple Python class to contain this information:
    class Flight(object):
    def __init__(self, flight=None, departure=None, arrival=None):
    self.flight = flight
    self.departure = departure
    self.arrival = arrival
    
  3. Now that we've created a simple model for our flight data, let's display it on the web page. To do that, let's add a 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.

  4. We can't click on the link yet because there is no controller, so let's build one and have it supply some sample 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.

    Looking up existing flights

    Now we can see this small bit of sample fl ight data.

  5. Let's add a search box to provide some minimal filtering capability
    @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.

    Looking up existing flights
  6. How would you utilize the text entered by the user to filter in the controller? Should we be case sensitive or perhaps relax that a bit? Which fields would you want to compare against if you were using it as a customer? Here is one way you could use it:
    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.

  7. One way to let the customers search is against all the data in 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.

    Looking up existing flights

What are some other search options you would consider to make this more sophisticated?

Moving from sample Python data to real Java data

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?

  • Would you attempt to bypass the Java system and talk directly to the database? What risks are involved with this approach?
  • Would you ditch the Python front end we just built and instead embark on a Java-based portal? What if this had been several months of effort?

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.

Tip

At the time of writing, CherryPy has issues running on Jython without some small tweaks. Sqlite support also isn't available yet. It's possible to tap Java components from Jython, but it's not realistic to assume all the Python components we plan to use will run.

Spring Python makes it easy to bridge this gap through its integration with the Pyro remoting library.

  1. To move our data access controller over to Java, we will also need to move the 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;
    }
    }
    
  2. Next, let's rewrite our controller in Java. To make things easier, we will use the original Spring Framework to code our query
    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"));
    }};
    }
    });
    }
    }
    

    Note

    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).

  3. In order to have our controller talk to a MySQL database as well as utilize the Spring Framework, we will need some extra 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.

  4. Before we can test this code, we need to setup our database:
    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'),
    
    Moving from sample Python data to real Java data

    The following screenshot shows this script being run to setup and load data:

  5. Now that we've migrated the controller code to Java and set up the schema, let's write a simple Jython script to print out the flight 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.

    Moving from sample Python data to real Java data

    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.

  6. Let's wire up our service using a Spring Python IoC configuration and then expose it using 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
    
  7. With this configuration, we are using the Apache Commons BasicDataSource to create a connection pool to access the MySQL database
  8. We create an instance of our Java-based FlightDataSystem. But instead of returning that, we inject it into our JythonFlightDataSystem, so that it can translate between Java and Python records
  9. We also create a PyroServiceExporter, and have it target our controller(). It is advertised under the name JavaFlightDataSystem
  10. We need a boot script to launch our Jython service
    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")
    
  11. Running Jython scripts with 3rd party libraries can be a little tricky. To make sure that the JVM can load classes from jar files, the jar files need to be passed in to the JVM as classpath entries
    jython -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
    
  12. The J argument feeds parameters to the JVM. This is necessary so that the class loaders can pull classes from the jar files
    Moving from sample Python data to real Java data

    We can see that Pyro has been started and is ready to serve data to any clients.

  13. With our Jython data feed running, we can now replace the original controller definition inside our application context with a PyroProxyFactory
    @Object
    def controller(self):
    java_system = PyroProxyFactory()
    java_system.service_url = 
    "PYROLOC://localhost:7766/JavaFlightDataSystem"
    return java_system
    
  14. By adding the following import statement, we are now able to re-launch our booking application in one shell, and the Jython service in another
    from springpython.remoting.pyro import *
    
  15. Now, let's re-launch our booking.py application using CPython, and look at the screens
Moving from sample Python data to real Java data

If we revisit the flight data page, it looks the same:

Moving from sample Python data to real Java data

However, looking closer at the console output from the Jython script, we see a message indicating our Java code is being called.

Moving from sample Python data to real Java data

The filtering logic has also been moved to the Java service, so we get the response.

Moving from sample Python data to real Java data
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.239.235