org.openmrs.module.formentry.FormEntryUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.formentry.FormEntryUtil.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.formentry;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.Drug;
import org.openmrs.Form;
import org.openmrs.FormResource;
import org.openmrs.User;
import org.openmrs.api.APIException;
import org.openmrs.api.AdministrationService;
import org.openmrs.api.context.Context;
import org.openmrs.customdatatype.CustomDatatype;
import org.openmrs.customdatatype.CustomDatatypeHandler;
import org.openmrs.customdatatype.CustomDatatypeUtil;
import org.openmrs.customdatatype.datatype.LongFreeTextDatatype;
import org.openmrs.util.FormConstants;
import org.openmrs.util.FormUtil;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.web.attribute.handler.LongFreeTextFileUploadHandler;

/**
 *
 */
public class FormEntryUtil {

    private static Log log = LogFactory.getLog(FormEntryUtil.class);

    /**
     * Cached directory where queue items are stored
     * 
     * @see #getFormEntryQueueDir()
     */
    private static File formEntryQueueDir = null;

    /**
     * Cached directory where gp says archive items are stored
     * 
     * @see #getFormEntryArchiveDir()
     */
    private static String formEntryArchiveFileName = null;

    /**
     * A FilenameFilter for xsl files.
     */
    private static FilenameFilter xslFilenameFilter = null;

    /**
     * Regex pattern for the end of the form in an XSL page (</body>)
     */
    public static Pattern endOfXSLPattern = Pattern.compile("(</body>)");

    private static String defaultXslt;

    /**
     * Expand the xsn defined by <code>xsnFileContents</code> into a temp dir The file returned by
     * this method should be deleted after use.
     * 
     * @param xsnFileContents byte array of xsn file content data
     * @return Directory in temp dir containing xsn contents
     * @throws IOException
     */
    public static File expandXsnContents(byte[] xsnFileContents) throws IOException {
        // copy the xsn contents to a temporary directory
        File tempXsnFromDatabaseDir = createTempDirectory("XSN-db-file");
        if (tempXsnFromDatabaseDir == null)
            throw new IOException("Failed to create temporary content directory");

        // copy the xsn contents to a new file
        File tmpXsnFromDatabaseFile = new File(tempXsnFromDatabaseDir, "tempContent.xsn");
        OutputStream out = new FileOutputStream(tmpXsnFromDatabaseFile);
        out.write(xsnFileContents);
        out.flush();
        out.close();

        String xsnFilePath = tmpXsnFromDatabaseFile.getAbsolutePath();

        File expandedContentsDir = null;
        try {
            expandedContentsDir = expandXsn(xsnFilePath);
        } finally {
            try {
                OpenmrsUtil.deleteDirectory(tempXsnFromDatabaseDir);
            } finally {
                // pass
            }
        }

        return expandedContentsDir;
    }

    /**
     * Expand the xsn at <code>xsnFilePath</code> into a temp dir
     * 
     * @param xsnFilePath
     * @return Directory in temp dir containing xsn contents
     * @throws IOException
     */
    public static File expandXsn(String xsnFilePath) throws IOException {
        File xsnFile = new File(xsnFilePath);
        if (!xsnFile.exists())
            return null;

        File tempDir = createTempDirectory("XSN");
        if (tempDir == null)
            throw new IOException("Failed to create temporary directory");

        StringBuffer cmdBuffer = new StringBuffer();

        if (OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM) {

            // retrieve the cabextract path from the runtime properties
            String cabextLocation = FormEntryConstants.FORMENTRY_CABEXTRACT_LOCATION;
            if (cabextLocation == null)
                cabextLocation = "/usr/local/bin/cabextract";

            File cabextractExecutable = new File(cabextLocation);
            if (!cabextractExecutable.exists()) {
                log.warn("cabextract not found at " + cabextLocation
                        + ", using cabextract from search path. SERIOUS: This may be a security violation! Please set the formentry.cabextract_location runtime property to the proper path");
                cabextLocation = "cabextract"; // ABK: hope to find it on the
                                               // path
            }

            cmdBuffer.append(cabextLocation + " -d ").append(tempDir.getAbsolutePath()).append(" ")
                    .append(xsnFilePath);
            execCmd(cmdBuffer.toString(), tempDir);
        } else {
            cmdBuffer.append("expand -F:* \"").append(xsnFilePath).append("\" \"").append(tempDir.getAbsolutePath())
                    .append("\"");
            execCmd(cmdBuffer.toString(), null);
        }

        return tempDir;
    }

    /**
     * Finds a folder path within resources and returns it as a File
     * 
     * @should throw a FileNotFoundException if the folderPath does not exist
     * @should return both directories and files
     * @param resourcePath path to the resource
     * @return file handle for the resource
     * @throws IOException
     */
    public static File getResourceFile(String resourcePath) throws IOException {

        log.debug("Getting URL directory: " + resourcePath);

        Class<FormEntryUtil> c = FormEntryUtil.class;

        // get the location of the starter documents
        URL url = c.getResource(resourcePath);
        if (url == null) {
            String err = "Could not open resource folder directory: " + resourcePath;
            log.error(err);
            throw new FileNotFoundException(err);
        }

        return OpenmrsUtil.url2file(url);
    }

    /**
     * Generates an expanded 'starter XSN'. This starter is essentially a blank XSN template to play
     * with in Infopath. Should be used similar to
     * <code>org.openmrs.module.formentry.FormEntryUtil.expandXsnContents(java.lang.String)</code>
     * Generates an expanded 'starter XSN'. This starter is essentially a blank XSN template to play
     * with in Infopath. Should be used similar to
     * <code>org.openmrs.formentry.FormEntryUtil.expandXsnContents(java.lang.String)</code>
     * 
     * @return File directory holding blank xsn contents
     * @throws IOException
     */
    public static File getExpandedStarterXSN() throws IOException {

        // temp directory to hold the new xsn contents
        File tempDir = FormEntryUtil.createTempDirectory("XSN-starter");
        if (tempDir == null)
            throw new IOException("Failed to create temporary directory");

        // iterate over and copy each file in the given folder
        File starterDir = getResourceFile(FormEntryConstants.FORMENTRY_STARTER_XSN_FOLDER_PATH);
        for (File f : starterDir.listFiles()) {
            File newFile = new File(tempDir, f.getName());
            FileChannel in = null, out = null;
            try {
                in = new FileInputStream(f).getChannel();
                out = new FileOutputStream(newFile).getChannel();
                in.transferTo(0, in.size(), out);
            } finally {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            }
        }

        return tempDir;
    }

    /**
     * Gets the current xsn file for a form. If the xsn is not found, the starter xsn is returned
     * instead The second array value in the returned object is a pointer to a temporary directory
     * containing the expanded xsn contents. This folder should be deleted after use
     * 
     * @param form
     * @param defaultToStarter true/false whether or not the starter xsn is returned when no current
     *            xsn is found
     * @return objects array: [0]: InputStream to form's xsn file or starter xsn if none, [1]:
     *         folder containing temporary expanded xsn files
     * @throws IOException
     */
    public static Object[] getCurrentXSN(Form form, boolean defaultToStarter) throws IOException {
        FormEntryService formEntryService = (FormEntryService) Context.getService(FormEntryService.class);

        // Find the form's xsn file data
        FormEntryXsn xsn = formEntryService.getFormEntryXsn(form);

        // The expanded the xsn
        File tempDir = null;

        if (xsn != null) {
            log.debug("Expanding xsn contents");
            tempDir = FormEntryUtil.expandXsnContents(xsn.getXsnData());
        } else if (defaultToStarter == true) {
            // use starter xsn as the
            log.debug("Using starter xsn");
            tempDir = FormEntryUtil.getExpandedStarterXSN();
        } else
            return new Object[] { null, tempDir };

        return new Object[] { compileXSN(form, tempDir), tempDir };
    }

    /**
     * Modifies schema, template.xml, and sample data, defaults, urls in <code>tmpXSN</code>
     * 
     * @param form
     * @param tempDir directory containing xsn files.
     * @return
     * @throws IOException
     */
    private static FileInputStream compileXSN(Form form, File tempDir) throws IOException {
        // Get Constants
        String schemaFilename = FormEntryConstants.FORMENTRY_DEFAULT_SCHEMA_NAME;
        String templateFilename = FormEntryConstants.FORMENTRY_DEFAULT_TEMPLATE_NAME;
        String sampleDataFilename = FormEntryConstants.FORMENTRY_DEFAULT_SAMPLEDATA_NAME;
        String defaultsFilename = FormEntryConstants.FORMENTRY_DEFAULT_DEFAULTS_NAME;
        String url = getFormAbsoluteUrl(form);

        // Generate the schema and template.xml
        FormXmlTemplateBuilder fxtb = new FormXmlTemplateBuilder(form, url);
        String template = fxtb.getXmlTemplate(false);
        String templateWithDefaultScripts = fxtb.getXmlTemplate(true);
        String schema = new FormSchemaBuilder(form).getSchema();

        // Generate and overwrite the schema
        File schemaFile = findFile(tempDir, schemaFilename);
        if (schemaFile == null)
            throw new IOException(
                    "Schema: '" + schemaFilename + "' cannot be null. Compiling xsn for form " + form);
        FileWriter schemaOutput = new FileWriter(schemaFile, false);
        schemaOutput.write(schema);
        schemaOutput.close();

        // replace template.xml with the generated xml
        File templateFile = findFile(tempDir, templateFilename);
        if (templateFile == null)
            throw new IOException(
                    "Template: '" + templateFilename + "' cannot be null. Compiling xsn for form " + form);
        FileWriter templateOutput = new FileWriter(templateFile, false);
        templateOutput.write(template);
        templateOutput.close();

        // replace defautls.xml with the xml template, including default scripts
        File defaultsFile = findFile(tempDir, defaultsFilename);
        if (defaultsFile == null)
            throw new IOException(
                    "Defaults: '" + defaultsFilename + "' cannot be null. Compiling xsn for form " + form);
        FileWriter defaultsOutput = new FileWriter(defaultsFile, false);
        defaultsOutput.write(templateWithDefaultScripts);
        defaultsOutput.close();

        // replace sampleData.xml with the generated xml
        File sampleDataFile = findFile(tempDir, sampleDataFilename);
        if (sampleDataFile == null)
            throw new IOException(
                    "Template: '" + sampleDataFilename + "' cannot be null. Compiling xsn for form " + form);
        FileWriter sampleDataOutput = new FileWriter(sampleDataFile, false);
        sampleDataOutput.write(template);
        sampleDataOutput.close();

        FormEntryUtil.makeCab(tempDir, tempDir.getAbsolutePath(), "new.xsn");

        File xsn = findFile(tempDir, "new.xsn");
        if (xsn == null)
            throw new IOException("MakeCab has failed because the generated 'new.xsn' file in the temp directory '"
                    + tempDir + "' cannot be null. Compiling xsn for form " + form);

        FileInputStream xsnInputStream = new FileInputStream(xsn);
        return xsnInputStream;
    }

    /**
     * Make an xsn (aka CAB file) with the contents of <code>tempDir</code>
     * 
     * @param tempDir
     */
    public static void makeCab(File tempDir, String outputDirName, String outputFilename) {
        // """calls MakeCAB to make a CAB file from DDF in tempdir directory"""

        StringBuffer cmdBuffer = new StringBuffer();

        // Special case : Linux operating sytem uses lcab utility
        if (OpenmrsConstants.UNIX_BASED_OPERATING_SYSTEM) {

            String lcabLocation = FormEntryConstants.FORMENTRY_LCAB_LOCATION;
            if (lcabLocation == null)
                lcabLocation = "/usr/local/bin/lcab";

            File lcabExecutable = new File(lcabLocation);
            if (!lcabExecutable.exists()) {
                log.warn("lcab not found at " + lcabLocation
                        + ", using lcab from search path. SERIOUS: This may be a security violation! Please set the formentry.lcab_location runtime property to the proper path.");
                lcabLocation = "lcab"; // ABK: not at the hard-coded location,
                                       // so hope to find it on the path
            }

            cmdBuffer.append(lcabLocation + " -rn ").append(tempDir).append(" ").append(outputDirName).append("/")
                    .append(outputFilename);

            // Execute command with working directory
            execCmd(cmdBuffer.toString(), tempDir);
        }
        // Otherwise, assume windows
        else {
            // create ddf
            FormEntryUtil.createDdf(tempDir, outputDirName, outputFilename);

            // Create makecab command
            cmdBuffer.append("makecab /F \"").append(tempDir.getAbsolutePath()).append("\\publish.ddf\"");

            // Execute command without working directory
            execCmd(cmdBuffer.toString(), null);

        }

    }

    /**
     * Convenience method to execute the given command in an environment agnostic manner
     * 
     * @param cmd command to execute
     * @param wd working directory (can be null)
     * @return command output
     */
    private static String execCmd(String cmd, File wd) {
        log.debug("executing command: " + cmd);
        StringBuffer out = new StringBuffer();
        try {
            // Needed to add support for working directory because of a linux
            // file system permission issue.
            // Could not create lcab.tmp file in default working directory
            // (jmiranda).
            Process p = (wd != null) ? Runtime.getRuntime().exec(cmd, null, wd) : Runtime.getRuntime().exec(cmd);

            // get the stdout
            out.append("Normal cmd output:\n");
            Reader reader = new InputStreamReader(p.getInputStream());
            BufferedReader input = new BufferedReader(reader);
            int readChar = 0;
            while ((readChar = input.read()) != -1) {
                out.append((char) readChar);
            }
            input.close();
            reader.close();

            // get the errout
            out.append("ErrorStream cmd output:\n");
            reader = new InputStreamReader(p.getErrorStream());
            input = new BufferedReader(reader);
            readChar = 0;
            while ((readChar = input.read()) != -1) {
                out.append((char) readChar);
            }
            input.close();
            reader.close();

            // wait for the thread to finish and get the exit value
            int exitValue = p.waitFor();

            if (log.isDebugEnabled())
                log.debug("Process exit value: " + exitValue);

        } catch (Exception e) {
            log.error("Error while executing command: '" + cmd + "'", e);
        }

        if (log.isDebugEnabled())
            log.debug("execCmd output: \n" + out.toString());

        return out.toString();
    }

    /**
     * Create a temporary directory with the given prefix and a random suffix
     * 
     * @param prefix String to insert before the random generated filename
     * @return New temp directory pointer
     * @throws IOException
     */
    public static File createTempDirectory(String prefix) throws IOException {
        String dirname = System.getProperty("java.io.tmpdir");
        if (dirname == null)
            throw new IOException("Cannot determine system temporary directory");

        File directory = new File(dirname);
        if (!directory.exists())
            throw new IOException("System temporary directory " + directory.getName() + " does not exist.");
        if (!directory.isDirectory())
            throw new IOException(
                    "System temporary directory " + directory.getName() + " is not really a directory.");

        File tempDir;
        do {
            String filename = prefix + System.currentTimeMillis();
            tempDir = new File(directory, filename);
        } while (tempDir.exists());

        if (!tempDir.mkdirs())
            throw new IOException("Could not create temporary directory '" + tempDir.getAbsolutePath() + "'");
        if (log.isDebugEnabled())
            log.debug("Successfully created temporary directory: " + tempDir.getAbsolutePath());

        tempDir.deleteOnExit();
        return tempDir;
    }

    /**
     * Finds the given filename in the given dir
     * 
     * @param dir
     * @param filename
     * @return File or null if not found
     */
    public static File findFile(File dir, String filename) {
        File file = null;
        for (File f : dir.listFiles()) {
            if (f.getName().equalsIgnoreCase(filename)) {
                file = f;
                break;
            }
        }
        return file;
    }

    /**
     * Create ddf that the makeCab exe uses to compile the xsn
     * 
     * @param xsnDir
     * @param outputDir
     * @param outputFileName
     */
    public static void createDdf(File xsnDir, String outputDir, String outputFileName) {
        String ddf = ";*** MakeCAB Directive file for " + outputFileName + "\n"
                + ".OPTION EXPLICIT         ; generate errors\n" + ".Set CabinetNameTemplate=" + outputFileName
                + "\n" + ".set DiskDirectoryTemplate=CDROM   ; all cabinets go in a single directory\n"
                + ".Set CompressionType=MSZIP      ; all files are compressed in cabinet files\n"
                + ".Set UniqueFiles=\"OFF\"\n" + ".Set Cabinet=on\n" + ".Set DiskDirectory1=\""
                + outputDir.replace("/", File.separator) // allow for either
                // direction of
                // slash
                + "\"\n";

        for (File f : xsnDir.listFiles())
            ddf += "\"" + f.getPath() + "\"\n";

        log.debug("ddf = " + ddf);

        File ddfFile = new File(xsnDir, "publish.ddf");
        try {
            FileWriter out = new FileWriter(ddfFile);
            out.write(ddf);
            out.close();
        } catch (IOException e) {
            log.error("Could not create DDF file to generate XSN archive", e);
        }
    }

    public static String getFormUriExtension(Form form) {
        return ".xsn";
    }

    public static String getFormUri(Form form) {
        return FormUtil.getFormUriWithoutExtension(form) + getFormUriExtension(form);
    }

    public static String getFormAbsoluteUrl(Form form) {
        // int endOfDomain = requestURL.indexOf('/', 8);
        // String baseUrl = requestURL.substring(0, (endOfDomain > 8 ?
        // endOfDomain : requestURL.length()));
        String serverURL = Context.getAdministrationService().getGlobalProperty(
                FormEntryConstants.FORMENTRY_GP_SERVER_URL,
                FormEntryConstants.FORMENTRY_GP_SERVER_URL + " cannot be empty");
        String baseUrl = serverURL + FormEntryConstants.FORMENTRY_INFOPATH_PUBLISH_PATH;
        return baseUrl + getFormUri(form);
    }

    public static String getFormSchemaNamespace(Form form) {
        String serverURL = Context.getAdministrationService().getGlobalProperty(
                FormEntryConstants.FORMENTRY_GP_SERVER_URL,
                FormEntryConstants.FORMENTRY_GP_SERVER_URL + " cannot be empty");
        String baseUrl = serverURL + FormEntryConstants.FORMENTRY_INFOPATH_PUBLISH_PATH;
        return baseUrl + "schema/" + form.getFormId() + "-" + form.getBuild();
    }

    public static String getSolutionVersion(Form form) {
        String version = form.getVersion();
        if (version == null || version.length() < 1 || version.length() > 4)
            version = "1.0.0";
        int numDots, i;
        for (numDots = 0, i = 0; (i = version.indexOf('.', i + 1)) > 0; numDots++)
            ;
        if (numDots < 2)
            for (i = numDots; i < 2; i++)
                version += ".0";
        if (form.getBuild() == null || form.getBuild() < 1 || form.getBuild() > 9999)
            form.setBuild(1);
        version += "." + form.getBuild();
        return version;
    }

    /**
     * @deprecated use org.openmrs.util#FormUtil.conceptToString(Concept, Locale)
     */
    public static String conceptToString(Concept concept, Locale locale) {
        // return org.openmrs.util.FormUtil.conceptToString(concept, locale);
        // TODO undeprecate this or change the name of the method to reflect the tuple it is generating
        return concept.getConceptId() + "^" + encodeUTF8String(concept.getName(locale).getName()) + "^"
                + FormConstants.HL7_LOCAL_CONCEPT;
    }

    /**
     * @deprecated use org.openmrs.util.FormUtil#drugToString(Drug)
     */
    public static String drugToString(Drug drug) {
        return org.openmrs.util.FormUtil.drugToString(drug);
    }

    // max length of HL7 message control ID is 20
    private static final int FORM_UID_LENGTH = 20;

    /**
     * Generates a uid in a length acceptable to hl7
     * 
     * @return relatively unique string
     */
    public static String generateFormUid() {
        return OpenmrsUtil.generateUid(FORM_UID_LENGTH);
    }

    /**
     * Gets the directory where the user specified their queues were being stored
     * 
     * @return directory in which to store queued items
     */
    public static File getFormEntryQueueDir() {

        if (formEntryQueueDir == null) {
            AdministrationService as = Context.getAdministrationService();
            String folderName = as.getGlobalProperty(FormEntryConstants.FORMENTRY_GP_QUEUE_DIR,
                    FormEntryConstants.FORMENTRY_GP_QUEUE_DIR_DEFAULT);
            formEntryQueueDir = OpenmrsUtil.getDirectoryInApplicationDataDirectory(folderName);
            if (log.isDebugEnabled())
                log.debug("Loaded formentry queue directory from global properties: "
                        + formEntryQueueDir.getAbsolutePath());
        }

        return formEntryQueueDir;
    }

    /**
     * Gets the directory where the user specified their archives were being stored
     * 
     * @param optional Date to specify the folder this should possibly be sorted into
     * @return directory in which to store archived items
     */
    public static File getFormEntryArchiveDir(Date d) {
        // cache the global property location so we don't have to hit the db
        // everytime
        if (formEntryArchiveFileName == null) {
            AdministrationService as = Context.getAdministrationService();
            formEntryArchiveFileName = as.getGlobalProperty(FormEntryConstants.FORMENTRY_GP_QUEUE_ARCHIVE_DIR,
                    FormEntryConstants.FORMENTRY_GP_QUEUE_ARCHIVE_DIR_DEFAULT);
        }

        // replace %Y %M %D in the folderName with the date
        String folderName = replaceVariables(formEntryArchiveFileName, d);

        // get the file object for this potentially new file
        File formEntryArchiveDir = OpenmrsUtil.getDirectoryInApplicationDataDirectory(folderName);

        if (log.isDebugEnabled())
            log.debug("Loaded formentry archive directory from global properties: "
                    + formEntryArchiveDir.getAbsolutePath());

        return formEntryArchiveDir;
    }

    /**
     * Replaces %Y in the string with the four digit year. Replaces %M with the two digit month
     * Replaces %D with the two digit day Replaces %w with week of the year Replaces %W with week of
     * the month
     * 
     * @param str String filename containing variables to replace with date strings
     * @return String with variables replaced
     */
    public static String replaceVariables(String str, Date d) {

        Calendar calendar = Calendar.getInstance();
        if (d != null)
            calendar.setTime(d);

        int year = calendar.get(Calendar.YEAR);
        str = str.replace("%Y", Integer.toString(year));

        int month = calendar.get(Calendar.MONTH) + 1;
        String monthString = Integer.toString(month);
        if (month < 10)
            monthString = "0" + monthString;
        str = str.replace("%M", monthString);

        int day = calendar.get(Calendar.DATE);
        String dayString = Integer.toString(day);
        if (day < 10)
            dayString = "0" + dayString;
        str = str.replace("%D", dayString);

        int week = calendar.get(Calendar.WEEK_OF_YEAR);
        String weekString = Integer.toString(week);
        if (week < 10)
            weekString = "0" + week;
        str = str.replace("%w", weekString);

        int weekmonth = calendar.get(Calendar.WEEK_OF_MONTH);
        String weekmonthString = Integer.toString(weekmonth);
        if (weekmonth < 10)
            weekmonthString = "0" + weekmonthString;
        str = str.replace("%W", weekmonthString);

        return str;
    }

    /**
     * @deprecated this method has been moved into the OpenmrsUtil class
     * @see org.openmrs.util.OpenmrsUtil#getOutFile(File,Date,User)
     */
    public static File getOutFile(File dir, Date date, User user) {
        return OpenmrsUtil.getOutFile(dir, date, user);
    }

    /**
     * Writes the give fileContentst to the given outFile
     * 
     * @param fileContents string to write to the file
     * @param outFile File to be overwritten with the given file contents
     * @throws IOException on write exceptions
     */
    public static void stringToFile(String fileContents, File outFile) throws IOException {
        FileWriter writer = new FileWriter(outFile);

        writer.write(fileContents);

        writer.close();
    }

    /**
     * Creates a zip file in <code>xsnDir</code> containing the <code>filesToZip</code> The name of
     * the dir is
     * 
     * @param xsnDir location to put the zip file
     * @param zipName name of the zip file. will be prepended with a timestamp
     * @param filesToZip list of files to zip into <code>zipName</code>
     * @throws IOException if the directory can't be written to
     */
    public static void moveToZipFile(File xsnDir, String zipName, List<File> filesToZip) throws IOException {

        if (filesToZip == null || filesToZip.size() < 1)
            return;

        // prepend a timestamp to the backup so we don't overwrite anything
        zipName = (new Date()).getTime() + zipName;

        if (!zipName.endsWith(".zip"))
            zipName = zipName + ".zip";

        File outFile = new File(xsnDir, zipName);
        if (!outFile.exists())
            outFile.createNewFile();

        FileOutputStream xsnDirOutStream = null;
        List<File> filesToDelete = new ArrayList<File>();

        try {
            xsnDirOutStream = new FileOutputStream(outFile);

            ZipOutputStream zos = new ZipOutputStream(xsnDirOutStream);
            ZipEntry zipEntry = null;

            for (File file : filesToZip) {

                try {
                    // string xsn data
                    String fileData = OpenmrsUtil.getFileAsString(file);

                    byte[] uncompressedBytes = fileData.getBytes();

                    // name this entry
                    zipEntry = new ZipEntry(file.getName());

                    // Add ZIP entry to output stream.
                    zos.putNextEntry(zipEntry);

                    // Transfer bytes from the formData to the ZIP file
                    zos.write(uncompressedBytes, 0, uncompressedBytes.length);

                    zos.closeEntry();

                    filesToDelete.add(file);
                } catch (IOException io) {
                    log.error("Unable to zip file: " + file.getAbsolutePath(), io);
                }
            }

            zos.close();
        } finally {
            if (xsnDirOutStream != null)
                xsnDirOutStream.close();

            for (File file : filesToDelete) {
                if (!file.delete())
                    file.deleteOnExit();
            }
        }
    }

    /**
     * The rebuilding process is basically just a download and reupload of the xsn. The point of
     * rebuilding would be to get a new schema into the xsn or to get new concepts/concept answers
     * into the form
     * 
     * @param form Form to rebuild the xsn for
     */
    public static void rebuildXSN(Form form) throws IOException {
        Object[] streamAndDir = FormEntryUtil.getCurrentXSN(form, true);
        InputStream formStream = (InputStream) streamAndDir[0];
        File tempDir = (File) streamAndDir[1];

        if (formStream == null)
            throw new IOException("The formstream for form: " + form + " should not be null (but it is)");

        PublishInfoPath.publishXSN(formStream);

        try {
            formStream.close();
        } catch (IOException ioe) {
        }
        try {
            OpenmrsUtil.deleteDirectory(tempDir);
        } catch (IOException ioe) {
        }
    }

    /**
     * Converts utf-8 characters into unicode escape characters
     * 
     * @param s the string to convert
     * @return the string with characters converted to unicode escapes with backslashes
     * @should encode Utf8 string
     */
    public static String encodeUTF8String(String s) {

        try {
            BufferedReader reader = null;
            BufferedWriter writer = null;
            ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
            try {
                reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(s.getBytes("UTF-8"))));
                writer = new BufferedWriter(new OutputStreamWriter(byteOutputStream, "ISO8859_1")); // or ASCII
                int temp;
                while ((temp = reader.read()) != -1) {
                    if (temp <= 0x7f)
                        writer.write(temp);
                    else
                        writer.write(toEscape((char) temp));
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            } finally {
                try {
                    if (reader != null)
                        reader.close();
                    if (writer != null)
                        writer.close();
                } catch (IOException ex) {
                    ;
                }
            }

            return byteOutputStream.toString("ISO8859-1");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            log.error("Unable to convert to unicode escape characters", e);
            return s;
        }
    }

    /**
     * Helper method for the encodeUTF8String method above.
     * 
     * @param c
     * @return
     */
    private static String toEscape(char c) { // instead of this method
                                             // charToHex() can be used
        int n = (int) c;
        String body = Integer.toHexString(n);
        // String body=charToHex(c); //instead of this the above can be used
        String zeros = "000";
        return ("\\u" + zeros.substring(0, 4 - body.length()) + body);
    } // end of method

    /**
     * adds a widget to the end of every XSL file in a form and publishes it
     * 
     * @should throw an IOException if the widget does not exist
     * @should use the starter XSN if no XSN currently exists for the form
     * @should only increment the build by one digit
     * @param form the form to be modified
     * @param widgetName the name of the widget (should exist already in the application context)
     * @return the modified form
     * @throws IOException
     */
    public static Form addWidgetToForm(Form form, String widgetName) throws IOException {
        FormEntryService formEntryService = (FormEntryService) Context.getService(FormEntryService.class);

        // get the widget's contents
        File widgetFolder = FormEntryUtil.getResourceFile(FormEntryConstants.FORMENTRY_INFOPATH_WIDGET_PATH);
        if (!OpenmrsUtil.folderContains(widgetFolder, widgetName))
            throw new IOException("cannot find widget folder for '" + widgetName + "'");

        widgetFolder = new File(widgetFolder.getAbsolutePath(), widgetName);
        if (!OpenmrsUtil.folderContains(widgetFolder, FormEntryConstants.FORMENTRY_INFOPATH_WIDGET_FILENAME))
            throw new IOException("cannot find widget file for '" + widgetName + "'");

        File widgetFile = new File(widgetFolder.getAbsolutePath(),
                FormEntryConstants.FORMENTRY_INFOPATH_WIDGET_FILENAME);
        String widgetContents = OpenmrsUtil.getFileAsString(widgetFile);

        // get the XSN extracted to a temporary location
        FormEntryXsn xsn = formEntryService.getFormEntryXsn(form);
        File tempDir = null;
        if (xsn == null)
            tempDir = FormEntryUtil.getExpandedStarterXSN();
        else
            tempDir = FormEntryUtil.expandXsnContents(xsn.getXsnData());

        // iterate over the XSL files
        String[] xslFilenames = tempDir.list(getXslFilenameFilter());
        for (String xslFilename : xslFilenames) {
            File xslFile = new File(tempDir.getAbsolutePath(), xslFilename);
            try {
                FormEntryUtil.addWidgetToXSLFile(widgetContents, xslFile);
            } catch (FileNotFoundException e) {
                log.error("update of relationship widget in \"" + xslFilename + "\" failed, because: " + e);
                e.printStackTrace();
            } catch (IOException e) {
                log.error("update of relationship widget in \"" + xslFilename + "\" failed, because: " + e);
                e.printStackTrace();
            }
        }

        // repackage the XSN
        InputStream newXSN = FormEntryUtil.compileXSN(form, tempDir);

        // publish the modified XSN
        form = PublishInfoPath.publishXSN(newXSN, form);

        // close the stream
        try {
            newXSN.close();
        } catch (IOException ioe) {
        }

        // delete the temp folder
        try {
            OpenmrsUtil.deleteDirectory(tempDir);
        } catch (IOException ioe) {
        }

        return form;
    }

    /**
     * adds a widget before the bottom of the <body/> of a XSL file
     * 
     * @should throw an IOException if the XSL file is not found
     * @should inject the widget's content directly before the body tag of each XSL in the form
     * @param widget the contents of the widget to inject
     * @param xslFile the XSL file to update
     * @throws IOException
     */
    private static void addWidgetToXSLFile(String widget, File xslFile) throws IOException {

        BufferedReader xslReader = new BufferedReader(new FileReader(xslFile));
        File tmpXslFile = File.createTempFile("infopath", ".xsltmp", xslFile.getParentFile());
        PrintWriter tmpXslWriter = new PrintWriter(new FileWriter(tmpXslFile));

        // drop the widget right before the </body> tag
        String line = xslReader.readLine();
        while (line != null) {
            Matcher m = endOfXSLPattern.matcher(line);
            if (m.find()) {
                tmpXslWriter.println(widget);
            }
            tmpXslWriter.println(line);
            line = xslReader.readLine();
        }

        // swap files
        tmpXslWriter.close();
        xslReader.close();
        xslFile.delete();
        if (!tmpXslFile.renameTo(xslFile)) {
            throw new IOException("Unable to rename xsl file from " + tmpXslFile.getAbsolutePath() + " to "
                    + xslFile.getAbsolutePath());
        }
    }

    /**
     * Lazy factory method of xslFilenameFilter. STOLEN from PublishInfoPath
     * 
     * @return a cached FilenameFilter for *.xsl files
     */
    private static FilenameFilter getXslFilenameFilter() {
        if (xslFilenameFilter == null) {
            xslFilenameFilter = new FilenameFilter() {

                public boolean accept(File dir, String name) {
                    return name.endsWith("xsl");
                }
            };
        }
        return xslFilenameFilter;
    }

    /**
     * Convenience method that returns the form's xslt
     * 
     * @param form the {@link Form} object
     * @return the xslt text
     * @should return the xslt for the form
     * @should return the default xslt if the form has no custom one
     */
    public static String getFormXslt(Form form) {
        String xslt = getXsltOrTemplate(form, true);
        if (StringUtils.isNotBlank(xslt))
            return xslt;

        return getDefaultXslt();
    }

    /**
     * Convenience method that returns the form's template
     * 
     * @param form the {@link Form} object
     * @return the template text
     * @should return null if the form has no template resource
     */
    public static String getFormTemplate(Form form) {
        return getXsltOrTemplate(form, false);
    }

    /**
     * Adds the specified resource to the specified form and saved it to the database
     * 
     * @param form the {@link Form} object
     * @param resource the resource to save
     * @param resourceNameSuffix the resource name suffix
     * @param handler TODO
     * @should save the form resource to the database
     * @should not add an xslt that is the same as the default
     */
    @SuppressWarnings("rawtypes")
    public static void saveXsltorTemplateFormResource(Form form, String resource, String resourceName,
            CustomDatatypeHandler handler) {
        //If this is an xslt and is the same as the default, ignore it
        if (FormEntryConstants.FORMENTRY_XSLT_FORM_RESOURCE_NAME.equals(resourceName)
                && StringUtils.isNotBlank(resource) && resource.equals(getDefaultXslt())) {
            return;
        }

        try {
            FormResource formResource = new FormResource();
            formResource.setForm(form);
            formResource.setName(resourceName);
            formResource.setDatatypeClassname(LongFreeTextDatatype.class.getName());
            if (handler == null)
                formResource.setPreferredHandlerClassname(LongFreeTextFileUploadHandler.class.getName());
            formResource.setValue(resource);
            Context.getFormService().saveFormResource(formResource);
        } catch (Exception e) {
            log.error("Error while saving form resource:", e);
        }
    }

    /**
     * Gets the default xslt
     * 
     * @return the xslt text
     * @should return the default xslt
     */
    public static String getDefaultXslt() {
        if (defaultXslt == null) {
            try {
                defaultXslt = IOUtils.toString(FormEntryUtil.class.getClassLoader()
                        .getResourceAsStream(FormEntryConstants.FORMENTRY_DEFAULT_XSLT_FILENAME));
            } catch (IOException e) {
                throw new APIException("Failed to load the default xslt:", e);
            }
        }

        return defaultXslt;
    }

    /**
     * Gets a an xslt or template form resource with the specified resource name suffix, the for
     * 
     * @param form the form
     * @param getXslt specifies if we are getting an xslt or template
     * @return the resource
     */
    @SuppressWarnings({ "rawtypes" })
    private static String getXsltOrTemplate(Form form, boolean getXslt) {
        String resourceName = (getXslt) ? FormEntryConstants.FORMENTRY_XSLT_FORM_RESOURCE_NAME
                : FormEntryConstants.FORMENTRY_TEMPLATE_FORM_RESOURCE_NAME;

        FormResource resource = Context.getFormService().getFormResource(form, resourceName);
        if (resource != null) {
            CustomDatatype datatype = CustomDatatypeUtil.getDatatype(resource);
            if (datatype != null) {
                return ((LongFreeTextDatatype) datatype).fromReferenceString(resource.getValueReference());
            }
        }

        return null;
    }
}