Migrating a Spring Java application to Python

In the previous section, I made the point that Spring Python doesn't require us to use XML. However, there is support for XML along with other formats we haven't looked at yet. Other formats are provided to make it easier for Spring Java developers to migrate to Python using what they already know.

Spring Python has two file parsers that read XML files containing object definitions. SpringJavaConfig reads Spring Java XML configuration files, and XMLConfig reads a format similar to that, but uniquely defined to offer Python-oriented options, such as support for dictionaries, tuples, sets, and frozen sets.

Note

SpringJavaConfig does not support all the features found in Spring Java's XML specifications, including the specialized namespaces, Spring Expression Language, nor properties. At this point in time, it is meant to be convenient tool to demonstrate simple integration between Python and Java.

Let's explore the steps taken if our wiki engine software had originally been written in Java, and we are exploring the option to migrate to Python. To make this as seamless as possible, we will run the Python bits in Jython.

Note

Jython is a Python compiler written in Java and designed to run on the JVM. Jython scripts can access both Python and Java-based libraries. However, Jython can not access Python extensions coded in C. Spring Python 1.1 runs on Jython 2.5.1. Information about download, installation, and other documentation of Jython can be found at http://jython.org.

  1. Let's assume that the Java code has a well defined interface for data access.
    public interface DataAccess {
    public int hits(String pageName);
    public int edits(String pageName);
    }
    
  2. For demonstration purposes, let's create this concrete Java implementation of that interface.
    public class MySqlDataAccess implements DataAccess {
    private JdbcTemplate jt;
    public MySqlDataAccess(DataSource ds) {
    this.jt = new JdbcTemplate(ds);
    }
    public int hits(String pageName) {
    return jt.queryForInt(
    "select HITS from METRICS " +
    "where PAGE = ?", pageName);
    }
    public int edits(String pageName) {
    return jt.queryForInt(
    "select count(*) from VERSIONS " +
    "where PAGE = ?", pageName);
    }
    }
    

    Note

    This Java code may appear contrived, considering MySqlDataAccess is supposed to be dependent on MySQL. It is for demonstration purposes that this over simplified version was created.

  3. Finally, let's look at a Java version of WikiService.
    public class WikiService {
    private DataAccess dataAccess;
    public DataAccess getDataAccess() {
    return this.dataAccess;
    }
    public void setDataAccess(DataAccess dataAccess) {
    this.dataAccess = dataAccess;
    }
    public double[] statistics(String pageName) {
    double hits = dataAccess.hits(pageName);
    double ratio = hits / dataAccess.edits(pageName);
    return new double[]{hits, ratio};
    }
    }
    
  4. A common way this would have been wired with Spring Java can be found in javaBeans.xml.
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="dataAccess" class="MySqlDataAccess"/>
    <bean id="wikiService" class="WikiService">
    <property name="dataAccess" ref="dataAccess"/>
    </bean>
    </beans>
    

    Note

    Note that this XML format is defined and managed by Spring Java, not Spring Python.

  5. Now that we can see all the parts of this pure Java application, the next step is to start it up using Spring Python and Jython.
    if __name__ == "__main__":
    from springpython.context import ApplicationContext
    from springpython.config import SpringJavaConfig
    ctx = ApplicationContext(SpringJavaConfig("javaBeans.xml"))
    service = ctx.get_object("wikiService")
    service.calculateWikiStats()
    

    Thanks to Jython's ability to transparently create both Python and Java objects, our Spring Python container can parse the object definitions in javaBeans.xml.

  6. As our development cycle continues, we find time to port MySqlDataAccess to Python.
  7. After doing so, we decide that we want to take advantage of more of Python's features, like tuples, sets, and frozen sets. The Spring Java format doesn't support these. Let's convert javaBeans.xml to Spring Python's XML format and save it in production.xml.
    <?xml version="1.0" encoding="UTF-8"?>
    <objects xmlns="http://www.springframework.org/springpython/schema/objects"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
    http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">
    <object id="data_access" class="Py.MySqlDataAccess"/>
    <object id="wiki_service" class="WikiService">
    <property name="dataAccess" ref="data_access"/>
    </object>
    </objects>
    

    This is very similar to the Spring Java format, and that's the point. By changing the header and replacing 'bean' with 'object', we can now use XMLConfig.

    if __name__ == "__main__":
    from springpython.context import ApplicationContext
    from springpython.config import XMLConfig
    ctx = ApplicationContext(XMLConfig("production.xml"))
    service = ctx.get_object("wiki_service")
    service.calculateWikiStats()
    

    We now have access to more options such as the following:

    <property name="some_set">
    <set>
    <value>Hello, world!</value>
    <ref object="SingletonString"/>
    <value>Spring Python</value>
    </set>
    </property>
    <property name="some_frozen_set">
    <frozenset>
    <value>Hello, world!</value>
    <ref object="SingletonString"/>
    <value>Spring Python</value>
    </frozenset>
    </property>
    <property name="some_tuple">
    <tuple>
    <value>Hello, world!</value>
    <ref object="SingletonString"/>
    <value>Spring Python</value>
    </tuple>
    </property>
    
  8. Having migrated this much, we decide to start coding some tests. We want to use the style shown at the beginning of this chapter where we swap out MySqlDataAccess with StubDataAccess using an alternative configuration. To avoid changing production.xml, we create an alternate configuration file test.xml.
    <?xml version="1.0" encoding="UTF-8"?>
    <objects xmlns="http://www.springframework.org/springpython/schema/objects"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
    http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">
    <object id="data_access" class="Py.StubDataAccess"/>
    </objects>
    
  9. Now let's code a test case using these two files for a specialized container. To do that, we give ApplicationContext with a list of configurations.
    import unittest
    from springpython.context import ApplicationContext
    from springpython.config import XMLConfig
    class WikiServiceTestCase(unittest.TestCase):
    def testHittingWikiService(self):
    ctx = ApplicationContext([XMLConfig("production.xml"),
    XMLConfig("test.xml")])
    service = ctx.get_object("wiki_service")
    results = service.statistics("stub page")
    self.assertEquals(10.0, results[0])
    self.assertEquals(2.0, results[1])
    

    The order of fi lenames is important. First, a list of defi nitions is read from production.xml. Next, more defi nitions are read from test.xml. Any new defi nitions with the same name as previous ones will overwrite the previous defi nitions. In our case, we overwrite data_access with a reference to our stub object.

  10. After all the success we have had with the Python platform, we decide to migrate the container definitions to pure Python. First, let's convert production.xml to WikiProductionAppConfig.
    from springpython.config import PythonConfig, Object
    from Py import MySqlDataAccess
    import WikiService
    class WikiProductionAppConfig(PythonConfig):
    def __init__(self):
    PythonConfig.__init__(self)
    @Object
    def data_access(self):
    return MySqlDataAccess()
    @Object
    def wiki_service(self):
    results = WikiService()
    results.dataAccess = self.data_access()
    return results
    
  11. Let's mix this with test.xml.
    import unittest
    from springpython.context import ApplicationContext
    from springpython.config import XMLConfig
    class WikiServiceTestCase(unittest.TestCase):
    def testHittingWikiService(self):
    ctx = ApplicationContext([WikiProductionAppConfig(),
    XMLConfig("test.xml")])
    service = ctx.get_object("wiki_service")
    results = service.statistics("stub page")
    self.assertEquals(10.0, results[0])
    self.assertEquals(2.0, results[1])
    

We simply replace XMLConfig("production.xml") with WikiProductionAppConfig(). ApplicationContext comfortably accepts any combination of definitions stored in different formats. Spring Python's flexible container gives us a fine grained control over how we store all our definitions.

We could continue and easily replace test.xml with something like WikiTestAppConfig written in pure Python.

This demonstrates how easy it is to convert things a piece at a time. We could have stopped earlier based on what we wanted to keep in Java and what we wanted to move to Python. If we move the rest of the Java parts to Python solutions, then we may opt to use either Python or Jython. The point is that XML support makes it easy to move from Java to Python with minimal impact.

Note

It is important to realize that just because we were starting with a Spring Java application doesn't mean this technique is only for Spring-based Java projects. In the previous example, we quickly moved past javaBeans.xml and wired the Java components using Spring Python's XML format.

..................Content has been hidden....................

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