Building A SCRUM StoryCards Generator - Roundup

In Part I we squeezed raw data out of an Excel sheet containing the SCRUM story cards. With a little bit of groovy this raw data got converted to XML. I the 2nd Part we created the XML needed as input for our PdfGenerator. In Part III we pimped an Apache FOP example and used an XSL-FO style sheet to render the PDF generated by the SCRUM StoryCards Generator. We ended up with two OSGi services:

  • pdfGenerator
  • storyCardBuilder

To include the OSGi services into our web application we ask Spring to handle all that low level stuff and inject the services directly into our controller:

<osgi:reference id="pdfGenerator" interface="de.datenkollektiv.util.fop.PdfGenerator" />
<osgi:reference id="storyCardBuilder" interface="de.datenkollektiv.scrum.cards.StoryCardBuilder" />

We want spring-osgi to deploy the story card generator into the web context path '/scrum'. This is done with a little hint in MANIFEST.MF

Import-Package:
...
  de.datenkollektiv.util.fop,
  de.datenkollektiv.util.poi,
  de.datenkollektiv.scrum.cards,
...
Web-ContextPath: /scrum

Next we configure the basic part of spring-mvc. Let's have a look at the OSGi aware web.xml:

<context-param>
  <param-name>contextClass</param-name>
  <param-value>org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
  <servlet-name>scrum</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

You certainly spotted the scope="session" inside the definition above. We store the uploaded data in the HTTP session.

One additional context listener is needed to work with session scoped beans.

<listener>
  <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

Now let's look into the spring-mvc configuration itself: The well known viewResolver and the Apache commons multipartResolver (for the Excel file upload - surprise, surprise ;-)

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
</bean>

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

The two controllers storyCardsController and fileUploadController are wired with the needed OSGi services and the session data per constructor for OSGi services and data alike.

<bean id="sessionData" class="de.datenkollektiv.scrum.web.SessionData" scope="session">
  <!-- this next element effects the proxying of the surrounding bean -->
  <!-- required to inject the scoped bean into controllers -->
  <aop:scoped-proxy />
</bean>

<bean id="storyCardsController" class="de.datenkollektiv.scrum.web.StoryCardsController">
  <property name="pdfGenerator" ref="pdfGenerator" />
  <property name="sessionData" ref="sessionData" />
</bean>
<bean id="fileUploadController" class="de.datenkollektiv.scrum.web.FileUploadController">
  <property name="storyCardBuilder" ref="storyCardBuilder" />
  <property name="sessionData" ref="sessionData" />
</bean>

Generating the story cards involves two steps. Upload the Excel sheet and generate the XML datastructure using the OSGi service storyCardBuilder is the first step: /upload.form. (Exception handling has been removed for better readability)

@RequestMapping(value = "/upload.form", method = RequestMethod.POST)
public String onSubmit(HttpServletRequest request, byte[] file) throws Exception {

  ExcelTemplate template = new ExcelTemplate(file);

If the uploaded Excel has been successfully transformed into the XML representation we present the user the /storycards.pdf download link where he can download/trigger the generation of the PDF.

@RequestMapping("/storycards.pdf")
public void generateStoryCards(HttpServletRequest request, HttpServletResponse response) throws Exception {

  Resource xsl = new DefaultResourceLoader().getResource("/StoryCard.xsl");
  InputStream inputStream = xsl.getInputStream();
  String data = sessionData.getData();
  byte[] pdf = pdfGenerator.generatePdf(new ByteArrayInputStream(data.getBytes()), inputStream);

  response.setContentType("application/pdf");
  byte[] buf = new byte[1024];
  OutputStream out = null;
  InputStream in = null;
  try {
    out = response.getOutputStream();
    in = new ByteArrayInputStream(pdf);
    int len;
    while ((len = in.read(buf)) < 0) {
      out.write(buf, 0, len);
    }
    in.close();
  } catch (IOException e) {
} finally {
...

How to customize the generated PDF with the help of a bundle fragment ist coming next...sorry, Moritz ;-)

Building A SCRUM StoryCards Generator - Part I

Building A SCRUM StoryCards Generator - Part II

Building A SCRUM StoryCards Generator - Part III

Building A SCRUM StoryCards Generator - Roundup


Header cover Photo by G. Crescoli