xc.mst.manager.processingDirective.DefaultServicesService.java Source code

Java tutorial

Introduction

Here is the source code for xc.mst.manager.processingDirective.DefaultServicesService.java

Source

/**
 * Copyright (c) 2009 eXtensible Catalog Organization
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the MIT/X11 license. The text of the
 * license can be found at http://www.opensource.org/licenses/mit-license.php and copy of the license can be found on the project
 * website http://www.extensiblecatalog.org/.
 *
 */

package xc.mst.manager.processingDirective;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import xc.mst.bo.processing.Job;
import xc.mst.bo.processing.ProcessingDirective;
import xc.mst.bo.provider.Format;
import xc.mst.bo.service.ErrorCode;
import xc.mst.bo.service.Service;
import xc.mst.constants.Constants;
import xc.mst.constants.Status;
import xc.mst.dao.DataException;
import xc.mst.dao.DatabaseConfigException;
import xc.mst.manager.BaseService;
import xc.mst.manager.IndexException;
import xc.mst.services.MetadataService;
import xc.mst.utils.AutoServiceReprocessingFileFilter;
import xc.mst.utils.LogWriter;
import xc.mst.utils.MSTConfiguration;
import xc.mst.utils.ServiceUtil;
import xc.mst.utils.Util;

/**
 * Provides implementation for service methods to interact with the services in the MST
 * 
 * @author Tejaswi Haramurali
 */
public class DefaultServicesService extends BaseService
        implements ServicesService, ApplicationListener<ApplicationEvent>, ApplicationContextAware {

    protected static Logger LOG = Logger.getLogger(Constants.LOGGER_GENERAL);

    /**
     * Constant signifying the parser is parsing the service's input formats
     */
    private static final String FILE_OUTPUT_FORMATS = "OUTPUT FORMATS";

    /**
     * Constant signifying the parser is parsing the service's output formats
     */
    private static final String FILE_INPUT_FORMATS = "INPUT FORMATS";

    /**
     * Constant signifying the parser is parsing the service's error messages
     */
    private static final String FILE_ERROR_MESSAGES = "ERROR MESSAGES";

    /**
     * Constant signifying the parser is parsing the service specific configuration
     */
    private static final String FILE_SERVICE_SPECIFIC = "SERVICE CONFIG";

    protected Map<String, ServiceEntry> serviceEntries = new HashMap<String, ServiceEntry>();
    protected Semaphore semaphore = new Semaphore(1);
    protected ApplicationContext applicationContext = null;
    protected static int loopCount = 0;

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public boolean isRootAC() {
        boolean acquired = this.semaphore.tryAcquire();
        if (acquired) {
            this.semaphore.release();
        }
        return acquired;
    }

    public void onApplicationEvent(ApplicationEvent event) {
        LOG.info("ac-event: " + event);
        /*
        try {
        if (event instanceof ContextRefreshedEvent) {
            boolean b = isRootAC();
            if (b) {
                // do nothing
            } else {
                this.semaphore.release();
            }
        }
        } catch (Throwable t) {
        throw new RuntimeException(t);
        }
        */
    }

    public ServiceEntry getServiceEntry(String name) {
        synchronized (this) {
            LOG.debug("entering getMetadataService " + name + " " + System.currentTimeMillis() + " " + this);
            if (!serviceEntries.containsKey(name)) {
                new ServiceEntry(name).start();
            }
            ServiceEntry se = serviceEntries.get(name);
            LOG.debug("exiting  getMetadataService " + name + " " + System.currentTimeMillis() + " " + this);
            return se;
        }
    }

    protected MetadataService getMetadataService(Service s) {
        MetadataService ms = (MetadataService) getServiceEntry(s.getName()).ac.getBean("Service");
        if (ms.getRepository().getName() == null) {
            ms.getRepository().setName(s.getName());
            ms.getRepository().setService(s);
        }
        return ms;
    }

    public Object getBean(String serviceName, String beanName) {
        return getServiceEntry(serviceName).ac.getBean(beanName);
    }

    class ServiceEntry {
        public Thread t = null;
        public ClassPathXmlApplicationContext ac = null;
        public String id = null;
        public ServiceEntry thisthis = null;

        public ServiceEntry(String id) {
            this.id = id;
        }

        public void start() {
            thisthis = this;
            try {
                this.t = new Thread() {
                    public void run() {
                        try {
                            String serviceFolder = MSTConfiguration.getUrlPath() + "/services/" + id;
                            List<URL> urls = new ArrayList<URL>();
                            String metaInfFolderStr = serviceFolder + "/META-INF";
                            File libFolder = new File(metaInfFolderStr + "/lib");
                            String classesFolderStr = metaInfFolderStr + "/classes/";
                            if (libFolder != null) {
                                if (libFolder.listFiles() != null) {
                                    for (File f : libFolder.listFiles()) {
                                        if (f.getName().endsWith(".jar")) {
                                            urls.add(f.toURI().toURL());
                                        }
                                    }
                                }
                                File f = new File(classesFolderStr);
                                URI uri = f.toURI();
                                URL url = uri.toURL();
                                url = new URL(url.toString() + "/");
                                urls.add(url);
                                URL[] urlsArr = urls.toArray(new URL[] {});
                                URLClassLoader loader = new URLClassLoader(urlsArr, getClass().getClassLoader());
                                MSTConfiguration.serviceBeingLoaded = id;
                                ac = new ClassPathXmlApplicationContext();
                                ac.setClassLoader(loader);
                                ac.setConfigLocation("xc/mst/services/spring-service.xml");
                                ac.setParent(applicationContext);
                                BufferedReader br = null;
                                try {
                                    br = new BufferedReader(new InputStreamReader(
                                            loader.getResourceAsStream("xc/mst/services/spring-service.xml")));
                                } catch (Throwable t) {
                                    LOG.debug("", t);
                                    semaphore.release();
                                    return;
                                }
                                StringBuilder sb = new StringBuilder();
                                String line = null;
                                while ((line = br.readLine()) != null) {
                                    sb.append(line + "\n");
                                }
                                br.close();
                                serviceEntries.put(id, thisthis);
                                Util util = (Util) config.getBean("Util");
                                util.setClassLoader(loader);
                                ac.refresh();
                                util.setClassLoader(null);
                            }
                        } catch (Throwable t) {
                            LOG.debug("", t);
                        } finally {
                            semaphore.release();
                        }
                        LOG.debug("done initting ac");
                    }
                };
                semaphore.acquire();
                t.start();
                semaphore.acquire();
                semaphore.release();
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }
    }

    protected void injectMetadataServices(List<Service> services) {
        for (Service service : services) {
            injectMetadataService(service);
        }
    }

    protected void injectMetadataService(Service service) {
        if (service.getMetadataService() == null) {
            service.setMetadataService(getMetadataService(service));
        }
    }

    /**
     * Returns a list of all services
     * 
     * @return List of Services
     * @throws DatabaseConfigException
     */
    public List<Service> getAllServices() throws DatabaseConfigException {
        List<Service> services = getServiceDAO().getAll();
        injectMetadataServices(services);
        return services;
    }

    /**
     * Returns a sorted list of services
     * 
     * @param sort
     *            determines whether the list of services is sorted in ascending or descending order
     * @param columnSorted
     *            column on which the rows are sorted
     * @return list of services
     * @throws DatabaseConfigException
     */
    public List<Service> getAllServicesSorted(boolean sort, String columnSorted) throws DatabaseConfigException {
        List<Service> services = getServiceDAO().getSorted(sort, columnSorted);
        injectMetadataServices(services);
        return services;
    }

    /**
     * Adds a new Service whose configuration details are present in the file
     * 
     * @param name
     * @throws xc.mst.dao.DataException
     * @throws java.io.IOException
     * @throws xc.mst.manager.processingDirective.ConfigFileException
     */
    public void addNewService(String name) throws DataException, IOException, ConfigFileException {
        PropertiesConfiguration props = null;

        try {
            props = new PropertiesConfiguration(MSTConfiguration.getUrlPath() + "/services/"
                    + getUtil().normalizeName(name) + "/META-INF/classes/xc/mst/services/custom.properties");

            String logFileName = getLogDAO().getById(Constants.LOG_ID_SERVICE_MANAGEMENT).getLogFileLocation();

            // The version of the service, which must appear in the second line of the configuration file
            String version = props.getString("service.version");
            /*
             * BDA 2010-12-06: This check adds a level of difficult and doesn't provide any rewards?
            if (!name.equals(getUtil().normalizeName(props.getString("service.name")))) {
            LOG.error("name: "+name);
            LOG.error("props.getString(service.name): "+getUtil().normalizeName(props.getString("service.name")));
            throw new RuntimeException("service folder name must match the name in the properties file.");
            }
            */

            if (version == null || version.length() == 0) {
                LogWriter.addError(logFileName,
                        "Error adding a new service: The second line of the service configuration file must be the service's version.");
                throw new ConfigFileException(
                        "The second line of the service configuration file must be the service's version.");
            }

            // The name of the service's class, which must appear in the fourth line of the configuration file
            String className = props.getString("service.classname");
            if (className == null || className.length() == 0) {
                LogWriter.addError(logFileName,
                        "Error adding a new service: The fourth line of the service configuration file must be the service's class name.");
                throw new ConfigFileException(
                        "The fourth line of the service configuration file must be the service's class name.");
            }

            // Populate the service BO
            Service service = new Service();
            service.setName(name);
            service.setVersion(version);
            service.setClassName(className);
            long latest = getLatestServiceFileTime(name);
            service.setServicesServiceLastModified(new Timestamp(latest));
            service.setHarvestOutLogFileName("logs" + MSTConfiguration.FILE_SEPARATOR + "harvestOut"
                    + MSTConfiguration.FILE_SEPARATOR + name + ".txt");
            service.setServicesLogFileName("logs" + MSTConfiguration.FILE_SEPARATOR + "service"
                    + MSTConfiguration.FILE_SEPARATOR + name + ".txt");
            service.setStatus(Status.NOT_RUNNING);

            String[] temp = new String[] { "input", "output" };
            for (int j = 0; j < temp.length; j++) {

                String[] formatNames = props.getStringArray(temp[j] + ".format.name");
                String[] formatSchemas = props.getStringArray(temp[j] + ".format.schemaLocation");
                String[] formatNamespaces = props.getStringArray(temp[j] + ".format.namespace");

                if (getUtil().arraysEqualInLength(formatNames, formatSchemas, formatNames)) {
                    for (int i = 0; i < formatNames.length; i++) {
                        Format format = getFormatDAO().getByName(formatNames[i]);

                        // If the format was not in the database, get it from the configuration file
                        if (format == null) {
                            format = new Format();
                            format.setName(formatNames[i]);
                            format.setSchemaLocation(formatSchemas[i]);
                            format.setNamespace(formatNamespaces[i]);
                            getFormatDAO().insert(format);
                        }
                        // Otherwise check whether or not the configuration file provided the same schema location and namespace for the format.
                        // Log a warning if not
                        else {
                            if (!format.getSchemaLocation().equals(formatSchemas[i]))
                                LogWriter.addWarning(logFileName,
                                        "The configuration file specified a schema location for the "
                                                + formatNames[i]
                                                + " format that differed from the one in the database. "
                                                + "The current schema location of " + format.getSchemaLocation()
                                                + " will be used " + "and the schema location " + formatSchemas[i]
                                                + " from the configuration file will be ignored.");

                            if (!format.getNamespace().equals(formatNamespaces[i]))
                                LogWriter.addWarning(logFileName,
                                        "The configuration file specified a namespace for the " + formatNames[i]
                                                + " format that differed from the one in the database. "
                                                + "The current namespace of " + format.getNamespace()
                                                + " will be used " + "and the namespace " + formatNamespaces[i]
                                                + " from the configuration file will be ignored.");
                        }

                        // Add the format we just parsed as an input format for the new service
                        if (temp[j].equals("input")) {
                            service.addInputFormat(format);
                        } else if (temp[j].equals("output")) {
                            service.addOutputFormat(format);
                        }
                    }
                } else {
                    throw new ConfigFileException(temp[j] + ".formats don't match in length");
                }
            }

            // Insert the service
            getServiceDAO().insert(service);

            // TODO : Error need not be stored in db. This code can be removed. It is never read from db.
            String[] errorCodes = props.getStringArray("error.code");
            String[] errorDescriptionFiles = props.getStringArray("error.descriptionFile");

            if (getUtil().arraysEqualInLength(errorCodes, errorDescriptionFiles)) {
                for (int i = 0; i < errorCodes.length; i++) {
                    ErrorCode errorCode = new ErrorCode();
                    errorCode.setErrorCode(errorCodes[i]);
                    errorCode.setErrorDescriptionFile(errorDescriptionFiles[i]);
                    errorCode.setService(service);

                    getErrorCodeDAO().insert(errorCode);
                }
            } else {
                throw new ConfigFileException("error codes don't match in length");
            }

            getRepositoryDAO().createRepository(service);
            MetadataService ms = getMetadataService(service);
            ms.getRepository().installOrUpdateIfNecessary(null, version);
            ms.setService(service);
            ms.install();
        } catch (DataException e) {
            LOG.error("DataException while adding a service: ", e);
            throw e;
        } catch (ConfigFileException e) {
            LOG.error("ConfigFileException while adding a service: ", e);
            throw e;
        } catch (Exception e) {
            LOG.error("Exception while adding a service: ", e);
        }
    }

    // called when GUI update made to service
    public void updateService(String name, Service service, boolean reprocessingRequired)
            throws DataException, IndexException, IOException, ConfigFileException {
        // Reload the service and confirm that it's not currently running.
        // Throw an error if it is
        service = getServiceDAO().getById(service.getId());
        if (service.getStatus().equals(Status.RUNNING) || service.getStatus().equals(Status.PAUSED)) {
            throw new DataException("Cannot update a service while it is running.");
            // TODO propagate to UI
        }

        String perviousVersion = service.getVersion();

        PropertiesConfiguration props = null;

        try {

            props = new PropertiesConfiguration(MSTConfiguration.getUrlPath() + "/services/" + name
                    + "/META-INF/classes/xc/mst/services/custom.properties");

            String logFileName = getLogDAO().getById(Constants.LOG_ID_SERVICE_MANAGEMENT).getLogFileLocation();

            // The version of the service, which must appear in the second line of the configuration file
            String version = props.getString("service.version");
            if (version == null || version.length() == 0) {
                LogWriter.addError(logFileName,
                        "Error adding a new service: The second line of the service configuration file must be the service's version.");
                throw new ConfigFileException(
                        "The second line of the service configuration file must be the service's version.");
            }

            // The name of the service's class, which must appear in the fourth line of the configuration file
            String className = props.getString("service.classname");
            if (className == null || className.length() == 0) {
                LogWriter.addError(logFileName,
                        "Error adding a new service: The fourth line of the service configuration file must be the service's class name.");
                throw new ConfigFileException(
                        "The fourth line of the service configuration file must be the service's class name.");
            }

            // Populate the service BO
            service.setName(name);
            service.setVersion(version);
            service.setClassName(className);
            LOG.debug(
                    "**** DefaultServicesService.updateService, about to see if need to setServicesServiceLastModified!");
            if (doesServiceFileTimeNeedUpdate(service)) {
                updateServiceLastModifiedTime(name, service);
                // TODO SHOULD we force reprocessing?
            }
            service.setHarvestOutLogFileName("logs" + MSTConfiguration.FILE_SEPARATOR + "harvestOut"
                    + MSTConfiguration.FILE_SEPARATOR + name + ".txt");
            service.setServicesLogFileName("logs" + MSTConfiguration.FILE_SEPARATOR + "service"
                    + MSTConfiguration.FILE_SEPARATOR + name + ".txt");
            service.getInputFormats().clear();
            service.getOutputFormats().clear();

            String[] temp = new String[] { "input", "output" };
            for (int j = 0; j < temp.length; j++) {

                String[] formatNames = props.getStringArray(temp[j] + ".format.name");
                String[] formatSchemas = props.getStringArray(temp[j] + ".format.schemaLocation");
                String[] formatNamespaces = props.getStringArray(temp[j] + ".format.namespace");

                if (getUtil().arraysEqualInLength(formatNames, formatSchemas, formatNames)) {
                    for (int i = 0; i < formatNames.length; i++) {
                        Format format = getFormatDAO().getByName(formatNames[i]);

                        // If the format was not in the database, get it from the configuration file
                        if (format == null) {
                            format = new Format();
                            format.setName(formatNames[i]);
                            format.setSchemaLocation(formatSchemas[i]);
                            format.setNamespace(formatNamespaces[i]);
                            getFormatDAO().insert(format);
                        }
                        // Otherwise check whether or not the configuration file provided the same schema location and namespace for the format.
                        // Log a warning if not
                        else {
                            if (!format.getSchemaLocation().equals(formatSchemas[i]))
                                LogWriter.addWarning(logFileName,
                                        "The configuration file specified a schema location for the "
                                                + formatNames[i]
                                                + " format that differed from the one in the database. "
                                                + "The current schema location of " + format.getSchemaLocation()
                                                + " will be used " + "and the schema location " + formatSchemas[i]
                                                + " from the configuration file will be ignored.");

                            if (!format.getNamespace().equals(formatNamespaces[i]))
                                LogWriter.addWarning(logFileName,
                                        "The configuration file specified a namespace for the " + formatNames[i]
                                                + " format that differed from the one in the database. "
                                                + "The current namespace of " + format.getNamespace()
                                                + " will be used " + "and the namespace " + formatNamespaces[i]
                                                + " from the configuration file will be ignored.");
                        }

                        // Add the format we just parsed as an input format for the new service
                        if (temp[j].equals("input")) {
                            service.addInputFormat(format);
                        } else if (temp[j].equals("output")) {
                            service.addOutputFormat(format);
                        }
                    }
                } else {
                    throw new ConfigFileException(temp[j] + ".formats don't match in length");
                }
            }

            // Insert the service
            getServiceDAO().update(service);

            String[] errorCodes = props.getStringArray("error.code");
            String[] errorDescriptionFiles = props.getStringArray("error.descriptionFile");

            if (getUtil().arraysEqualInLength(errorCodes, errorDescriptionFiles)) {
                for (int i = 0; i < errorCodes.length; i++) {

                    ErrorCode errorCode = getErrorCodeDAO().getByErrorCodeAndService(errorCodes[i], service);
                    if (errorCode == null) {
                        errorCode = new ErrorCode();
                        errorCode.setErrorCode(errorCodes[i]);
                        errorCode.setErrorDescriptionFile(errorDescriptionFiles[i]);
                        errorCode.setService(service);
                        getErrorCodeDAO().insert(errorCode);
                    } else {
                        errorCode.setErrorDescriptionFile(errorDescriptionFiles[i]);
                        getErrorCodeDAO().update(errorCode);
                    }

                }
            } else {
                throw new ConfigFileException("error codes don't match in length");
            }

            // getRepositoryDAO().createRepository(service);
            MetadataService ms = getMetadataService(service);
            ms.getRepository().installOrUpdateIfNecessary(perviousVersion, version);
            ms.install();

            // TODO what does below line do? Is it necessary? Should it be here or moved to Service Reprocess thread?
            ServiceUtil.getInstance().checkService(service.getId(), Status.NOT_RUNNING, true);

            // Schedule a job to reprocess records through new service
            if (reprocessingRequired)
                reprocessService(service);
        } catch (ConfigurationException e) {
            LOG.error("ConfigFileException while adding a service: ", e);
            throw new ConfigFileException(e.getMessage());
        }
    }

    /**
     * Inserts a service into the MST
     * 
     * @param service
     *            service object
     * @throws xc.mst.dao.DataException
     */
    public void insertService(Service service) throws DataException {
        getServiceDAO().insert(service);
    }

    /**
     * Deletes a service from the MST. This method has to be used when the service has no processed records.
     * 
     * @param service
     *            service to be deleted
     * @throws xc.mst.dao.DataException
     */
    public void deleteService(Service service) throws DataException {
        // Get all Processing Directives that has this service as source service
        List<ProcessingDirective> processingDirectives = getProcessingDirectiveService()
                .getBySourceServiceId(service.getId());

        // Delete Processing Directive that has this service as source service
        for (ProcessingDirective processingDirective : processingDirectives) {
            getProcessingDirectiveService().deleteProcessingDirective(processingDirective);
        }

        // Delete service
        getServiceDAO().delete(service);
        ServiceEntry se = serviceEntries.get(service.getName());
        se.ac.destroy();
        serviceEntries.remove(service.getName());
        getRepositoryDAO().deleteSchema(service.getName());
    }

    /**
     * Deletes a service and its records by scheduling a job
     * 
     * @param service
     *            service to be deleted
     */
    public void deleteServiceAndRecordsByJob(Service service) throws DataException {
        service.setDeleted(true);
        getServiceDAO().update(service);
        try {
            Job job = new Job(service, 0, Constants.THREAD_DELETE_SERVICE);
            JobService jobService = (JobService) config.getBean("JobService");
            job.setOrder(jobService.getMaxOrder() + 1);
            jobService.insertJob(job);
        } catch (DatabaseConfigException dce) {
            LOG.error("DatabaseConfig exception occured when ading jobs to database", dce);
        }

    }

    /**
     * Updates the details related to a service
     * 
     * @param service
     *            service object
     * @throws xc.mst.dao.DataException
     */
    public void updateService(Service service) throws DataException {
        getServiceDAO().update(service);
    }

    /**
     * Returns a service by ID
     * 
     * @param serviceId
     *            service ID
     * @return Service object
     * @throws DatabaseConfigException
     */
    public Service getServiceById(int serviceId) throws DatabaseConfigException {
        Service s = getServiceDAO().getById(serviceId);
        if (s != null) {
            injectMetadataService(s);
            List<Integer> setIds = getRepositoryDAO().getSetIds(s.getName());
            if (setIds != null) {
                for (Integer setId : setIds) {
                    xc.mst.bo.provider.Set set = getSetService().getSetById(setId);
                    if (!s.getOutputSets().contains(set)) {
                        s.getOutputSets().add(set);
                    }
                }
            }
        }
        return s;
    }

    /**
     * Returns a service by name
     * 
     * @param serviceName
     *            name of the service
     * @return service object
     * @throws DatabaseConfigException
     */
    public Service getServiceByName(String serviceName) throws DatabaseConfigException {
        Service s = getServiceDAO().getByServiceName(serviceName);
        if (s != null) {
            s.setMetadataService(getMetadataService(s));
            s.getMetadataService().setService(s);
        }
        return s;
    }

    /**
     * Given a BufferedReader for a File, skip to the next line in the file
     * which is neither a comment nor whitespace
     * 
     * @param in
     *            The BufferedReader for the file
     * @return The first non-comment non-whitespace line in the file, or null if we reached the end of the file
     * @throws IOException
     *             If an error occurred while reading the file
     */
    private String consumeCommentsAndWhitespace(BufferedReader in) throws IOException {
        while (in.ready()) {
            String line = in.readLine(); // A line from the configuration file

            // If the line is a valid line, return it
            if (!line.startsWith("#") && line.trim().length() > 0)
                return line.trim();
        }

        // If we got here we reached the end of the file, so return null
        return null;
    }

    public Collection<String> getServicesAvailableForInstall() {
        List<String> availableServices = new ArrayList<String>();
        File dir = getServiceDir();
        File[] fileList = dir.listFiles();

        Set<String> allServices = new HashSet<String>();
        try {
            List<Service> services = getAllServices();
            for (Service s : services) {
                allServices.add(s.getName());
            }
        } catch (Throwable t) {
            LOG.error("", t);
        }

        for (File file : fileList) {
            if (file.isDirectory() && !allServices.contains(file.getName())) {
                boolean nameIsAcceptable = true;
                for (byte b : file.getName().getBytes()) {
                    if (!(Character.isDigit((char) b) || Character.isLetter((char) b))) {
                        nameIsAcceptable = false;
                    }
                }
                if (nameIsAcceptable) {
                    String serviceName = file.getName();
                    availableServices.add(serviceName);
                }
            }
        }

        return availableServices;
    }

    private static File getServiceDir() {
        // this had a hard-coded '/' file separator in code before so leave it in for now.
        File dir = new File(MSTConfiguration.getUrlPath() + "/services");
        return dir;
    }

    private static Collection<File> getAllServiceFiles(File file) {
        Collection<File> all = new ArrayList<File>();
        all = addFilesToScanRecursively(file, all);
        return all;
    }

    private static Collection<File> addFilesToScanRecursively(File file, Collection<File> all) {
        // AutoServiceReprocessingFileFilter
        final File[] children = file.listFiles(new AutoServiceReprocessingFileFilter());
        if (children != null) {
            for (File child : children) {
                all.add(child);
                addFilesToScanRecursively(child, all);
            }
        }
        return all;
    }

    // want latest date of an actual file within service, disqualify directory timestamps as not being relevant.
    // Also filter based on file types acceptable found in MST config property:
    // (regexpOfFilesToScanForAutoServiceReprocessing)
    //
    private long getLatestServiceFileTime(String name) {
        File dir = new File(getServiceDir(), name);
        long latest = 0l;
        final Collection<File> serviceFiles = getAllServiceFiles(dir);
        for (File f : serviceFiles) {
            if (!f.isDirectory() && f.lastModified() > latest) {
                latest = f.lastModified();
                LOG.debug("*** Latest filesystem service file found! Name=" + f.getName() + " Date="
                        + new Date(latest));
            }
        }
        return latest;
    }

    // When you grab the time out of the database, it was stored as a Timestamp, and the db driver seems to clip (0) the
    // milliseconds at this point in time, so we need to be careful we compare apples to apples, so zero out the milliseconds
    // from time we receive from file system scan.
    // (Normalize the times by dropping milliseconds.)
    //
    public boolean doesServiceFileTimeNeedUpdate(Service service) {
        Calendar calCurrentLatest = Calendar.getInstance();
        Calendar calNewLatest = Calendar.getInstance();

        final String name = service.getName();
        final long currentLatest = service.getServicesServiceLastModified().getTime();
        calCurrentLatest.setTimeInMillis(currentLatest);
        calCurrentLatest.set(Calendar.MILLISECOND, 0);

        long newLatest = getLatestServiceFileTime(name);
        calNewLatest.setTimeInMillis(newLatest);
        calNewLatest.set(Calendar.MILLISECOND, 0);

        // show latest date we found
        LOG.debug("***** doesServiceFileTimeNeedUpdate? current latest time=" + new Date(currentLatest));

        // show comparison of new and old long date values found, with milliseconds zeroed out.
        LOG.debug("***** USE CAL: doesServiceFileTimeNeedUpdate? current latest="
                + calCurrentLatest.getTimeInMillis() + " new latest=" + calNewLatest.getTimeInMillis());

        return calNewLatest.getTimeInMillis() > calCurrentLatest.getTimeInMillis();
    }

    public void updateServiceLastModifiedTime(String name, Service service) {
        long latest = getLatestServiceFileTime(name);
        service.setServicesServiceLastModified(new Timestamp(latest));
        LOG.debug("***** latest time returned on a file =" + latest + " date=" + new Date(latest));
        LOG.debug(
                "***** DefaultServicesService.updateServiceLastModifiedTime, just setServicesServiceLastModified!");
    }

    public void reprocessService(Service service) {
        // TODO
        // THREAD_SERVICE_REPROCESS was empty (no assoc. code run in Scheduler)
        // Do you REALLY want to reprocess, if so, how? THREAD_SERVICE ? Set a PD...(see scheduler code that does this)
        try {
            Job job = new Job(service, 0, Constants.THREAD_SERVICE_REPROCESS);
            JobService jobService = (JobService) config.getBean("JobService");
            job.setOrder(jobService.getMaxOrder() + 1);
            jobService.insertJob(job);
        } catch (DatabaseConfigException dce) {
            LOG.error("DatabaseConfig exception occured when ading jobs to database", dce);
        }
    }
}