Sample custom transport service

The following example shows how to write a custom transport. In this case, the example shows how to write File Transport.

File Transport supports the following parameters:

BaseDirectory
For each call to the transport method of the File transport, a separate subdirectory is created in which the export document is written.
FileName
The name of the file to which the form export document is written.

You must include these parameters in the File transport service declaration in the WebForms configuration file (webforms_conf.xml).

File transport writes the export document to a text file. The name of this text file can be overwritten as a parameter in the transport service declaration.

To write a custom transport for File Transport, do the following:

  1. Implement the FileTransport class. This class must implement the following interfaces:

    • the com.tridion.webforms.transport.Transport interface
    • the Configurable interface, which retrieves the values of the parameters listed above

    To implement these interfaces, implement the following two methods:

    • configure(com.tridion.configuration.Configuration)
    • transport(org.wc3.dom.Document, java.util.Map, java.util.List)
    package com.tridion.webforms.transport;
    import org.w3c.dom.Document;
    import java.util.List;
    import com.tridion.configuration.Configurable;
    import com.tridion.configuration.Configuration;
    import com.tridion.configuration.ConfigurationException;
    import javax.xml.transform.TransformerException;
    
    public class FileTransport implements Transport, Configurable {
    
      public void configure(Configuration configuration) throws ConfigurationException {
      }
    
      public void transport(Document document, Map attachmentMap, List parameters) 
        throws TransportException {
      }
    }
  2. In the configure() method, retrieve the base directory and filename as follows:

    public class FileTransport implements Transport, Configurable {
      private String baseDirectory;
      private String defaultFileName;
      public void configure(Configuration configuration) throws ConfigurationException {
        baseDirectory = configuration.getStringParameterValue("BaseDirectory");
        defaultFileName = configuration.getStringParameterValue("FileName");
      }
      public void transport(Document document, Map attachmentMap, List parameters)
        throws TransportException {
      }
    }
  3. Within the directory specified by the BaseDirectory parameter, create a separate subdirectory for each call to the transport method. This directory stores the Form export document and any attachments. As a result, you can distinguish between different sessions and prevent overwriting existing files.
  4. The generateExportDirectoryName() method generates the name of a subdirectory:

    private static final String PREFIX_EXPORT_DIRECTORY = "export_";
    private long lastGeneratedExportDirectoryTime = Long.MIN_VALUE;
    private synchronized String generateExportDirectoryName() {
      long time = System.currentTimeMillis();
      if (time <= lastGeneratedExportDirectoryTime) {
        time = lastGeneratedExportDirectoryTime + 1;
      }
      lastGeneratedExportDirectoryTime = time;
      return PREFIX_EXPORT_DIRECTORY + time;
    }
  5. Implement the transport() method to do the following:

    • Create a subdirectory in the base directory using the generateExportDirectoryName() method.
    • Write the form export document to file.
    • Store the supplied attachments.
    public void transport(Document document, Map attachmentMap, List parameters)
      throws TransportException {
      // (1) construct the directory in which to store the document and attachments
      String directoryName = baseDirectory + File.separator + generateExportDirectoryName();
      File directory = new File(directoryName);
      if (!directory.mkdir()) {
        // the directory could not be created; if the directory already exists then something went
        // seriously wrong with generating a unique name of that directory
        if (directory.exists()) {
          String errorMessage = "Directory '" + directoryName + "' already exists";
          throw new IllegalStateException(errorMessage);
        }
        // something else went wrong (probably an I/O error or no permissions)
        String errorMessage = "Failed to create directory: " + directoryName;
        throw new TransportException(errorMessage);
      }
      // (2) write the form data
      writeFormData(new File(directory, defaultFileName), document);
      // (3) write any attachments
      writeAttachments(directory, attachmentMap);
    }
  6. Implement the writeFormData() method:

    private void writeFormData(File file, Document document) throws TransportException {
      // write the document to the file
      try {
        Source source = new DOMSource((Document) content);
        Result result = new StreamResult(file);
        TransformerFactory.newInstance().newTransformer().transform(source, result);
      } catch (TransformerException exception) {
        String errorMessage = "An error occurred while writing the document to file: " +
        file.getName();
        throw new TransportException(errorMessage, exception);
      } finally {
        if (out != null) {
          try {
            out.close();
          } catch (IOException exception) {
          String errorMessage = "An error occurred while closing file: " + file.getName();
          throw new TransportException(errorMessage, exception);
        }
      }
    }
  7. Store the attachments. Each attachment is represented as a com.tridion.webforms.storage.Attachment instance. This class contains methods to retrieve the attachment data. Use the writeAttachments() method to store the attachments. The first parameter defines the directory in which the attachments must be stored and the second parameter is the map containing the attachments.

    public void writeAttachments(File directory, Map attachmentMap)
      throws TransportException {
      for (Iterator attachmentIterator = attachmentMap.keySet().iterator();
        attachmentIterator.hasNext();) {
        String identifier = (String) attachmentIterator.next();
        Attachment attachment = (Attachment) attachmentMap.get(identifier);
        // open the file to which the attachment must be written
        File attachmentFile = new File(directory, attachment.getFilename());
        FileOutputStream fileOutputStream;
        try {
          fileOutputStream = new FileOutputStream(attachmentFile);
        } catch (FileNotFoundException exception) {
          String errorMessage = "Unable to open file: " + attachmentFile;
          throw new TransportException(errorMessage, exception);
        }
        // write the attachment
        try {
          // get an InputStream containing the attachment data
          InputStream inputStream;
          try {
            inputStream = attachment.getInputStream();
          } catch (StorageException exception) {
            String errorMessage = "Failed to get InputStream from attachment.";
            throw new TransportException(errorMessage, exception);
          }
          // write the attachment data to the output stream
          try {
            byte[] buffer = new byte[8192];
            int read = 0;
            while ((read = inputStream.read(buffer)) > 0) {
              fileOutputStream.write(buffer, 0, read);
            }
          } catch (IOException exception) {
            String errorMessage = "An error occurred while writing attachment: " + identifier;
            throw new TransportException(errorMessage, exception);
          } finally {
            // close the inputstream
            try {
              inputStream.close();
            } catch (IOException exception) {
              String errorMessage = "An error occurred while closing InputStream from attachment.";
              throw new TransportException(errorMessage, exception);
            }
          }
        } finally {
          // close the output stream
          try {
            fileOutputStream.close();
          } catch (IOException exception) {
            String errorMessage = "An error occurred while closing file: " + attachmentFile;
            throw new TransportException(errorMessage, exception);
          }
        }
      }
    }

    This method iterates over all attachments in the java.util.Map instance. Each attachment is stored in a separate file. The name of this file is retrieved by calling the getFilename() method on the current com.tridion.webforms.storage.Attachment instance. A java.io.InputStream from which the actual data of the attachment can be read is retrieved by calling the getInputStream() method.

  8. Declare the FileTransport in the WebForms configuration file as follows:

    <TransportServiceManager>
      <TransportService Name="File"
        Class="com.tridion.webforms.transport.FileTransport">
        <Param>
          <Name>BaseDirectory</Name>
          <Value>C:\home\WebForms\PS\data</Value>
        </Param>
        <Param>
          <Name>FileName</Name>
          <Value>export.xml</Value>
        </Param>
      </TransportService>
    </TransportServiceManager>
  9. The service Business Rule refers to this transport service as follows:

    <transport>
      <getFormExportDocument/>
      <getFormAttachmentMap/>
      <service>
        <literal>File</literal>
      </service>
    </transport>

    Note how the File parameter for service corresponds to the File value of the Name attribute of the TransportService element in the previous step.

  10. If you want to overwrite the file name in the service Business Rule, change the implementation of the transport method as follows:

    public void transport(Document document, Map attachmentMap, List parameters) 
      throws TransportException {
      // (1) construct the directory in which to store the document and attachments
      String directoryName = baseDirectory + File.separator + generateExportDirectoryName();
      File directory = new File(directoryName);
      if (!directory.mkdir()) {
        // the directory could not be created; if the directory already exists then something went
        // seriously wrong with generating a unique name of that directory
        if (directory.exists()) {
          String errorMessage = "Directory '" + directoryName + "' could not be created because it
           already exists";
          throw new IllegalStateException(errorMessage);
        }
        // something else went wrong (probably an I/O error or permissions)
        String errorMessage = "Failed to create directory: " + directoryName;
        throw new TransportException(errorMessage);
      }
      // determine the filename of the document
      String fileName;
      if (parameters.size() > 0) {
        fileName = (String) parameters.get(0);
      } else {
        fileName = defaultFileName;
      }
      // (2) write the form data
      writeFormData(new File(directory, fileName), document);
      // (3) write any attachments
      writeAttachments(directory, attachmentMap);
    }
  11. Determine the file name if the parameter list contains at least one element. If so, the first element is used as the filename, otherwise the default filename specified in the transport service declaration is used.
  12. You can use the following application of the service Business Rule:

    <transport>
      <getFormExportDocument/>
      <getFormAttachmentMap/>
      <service>
        <literal>File</literal>
        <literal>export2.xml</literal>
      </service>
    </transport>

    In this example, the form export document is written to export2.xml.

  13. Add an optional third parameter in the FileTransport configuration that contains the URI of an XSLT stylesheet that must be applied to the form export document. Assume that the name of the parameter is Transformation. Extend the configure() method as follows:

    private String fileName;
    private URI xsltStyleSheetURI;
    
    public void configure(Configuration configuration) throws ConfigurationException {
      fileName = configuration.getStringParameterValue("FileName");
      String transformation =
        configuration.getStringParameterValue("Transformation", null);
      if (transformation != null) {
        try {
          xsltStyleSheetURI = new URI(transformation);
        } catch (URISyntaxException exception) {
          String errorMessage = "Invalid URI specified for XSLT stylesheet: " +
            xsltStyleSheetURI;
          LogFactory.error(errorMessage, exception);
          throw new ConfigurationException(errorMessage, exception);
        }
      }
    }
  14. The configure(com.tridion.configuration.Configuration) method checks if the Transformation parameter is set. If so, it constructs a URI object given its value.

    The transport method applies the XSLT style sheet to the Form export document using the com.tridion.webforms.transport.XSLTProcessor class.

    The writeTransportData(java.io.File, org.w3c.dom.Document) method looks as follows:

    private void writeFormData(File file, Document document) throws TransportException {
      // determine the content that must be written to file
      Object content;
      if (xsltStyleSheetURI != null) {
        XSLTProcessor processor = new XSLTProcessor(xsltStyleSheetURI);
        content = processor.processTransportDocument(document);
      } else {
        content = document;
      }
      // write the content to the file if it is a DOM document
      if (content instanceof Document) {
        try {
          Source source = new DOMSource((Document) content);
          Result result = new StreamResult(file);
          TransformerFactory.newInstance().newTransformer().transform(source, result);
        } catch (TransformerException exception) {
          String errorMessage = "An error occurred while writing the document to file: " +
            file.getName();
          throw new TransportException(errorMessage, exception);
        }
      // write the content to the file if it is not a DOM document
      } else {
        BufferedWriter out = null;
        try {
          out = new BufferedWriter(new FileWriter(file));
          out.write(content.toString());
        } catch (IOException exception) {
          String errorMessage = "An error occurred while writing the document to file: " +
            file.getName();
          throw new TransportException(errorMessage, exception);
        } finally {
          if (out != null) {
            try {
              out.close();
            } catch (IOException exception) {
              String errorMessage = "An error occurred while closing file: " + file.getName();
              throw new TransportException(errorMessage, exception);
            }
          }
        }
      }
    }
  15. You can now extend the transport service declaration in the WebForms configuration file by adding the Transformation parameter with the URI of a stylesheet as its value (for example, defaultss.xsl).

    <TransportServiceManager>
      <TransportService Name="File" 
        Class="com.tridion.webforms.transport.FileTransport">
        <Param>
          <Name>FileName</Name>
          <Value>export.xml</Value>
        </Param>
        <Param>
          <Name>Transformation</Name>
          <Value>file:///C:/Stylesheets/defaultss.xsl</Value>
        </Param>
      </TransportService>
    </TransportServiceManager>