org.alfresco.reporting.ReportingHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.reporting.ReportingHelper.java

Source

/**
 * Copyright (C) 2011 - 2013 Alfresco Business Reporting project
 *
 * This file is part of the Alfresco Business Reporting project.
 *
 * Licensed under the GNU LGPL, Version 3.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://www.gnu.org/licenses/lgpl.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.alfresco.reporting;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.TimeZone;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.reporting.execution.ReportTemplate;
import org.alfresco.reporting.execution.ReportingContainer;
import org.alfresco.reporting.execution.ReportingRoot;
import org.alfresco.reporting.util.resource.HierarchicalResourceLoader;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.ResultSetRow;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ReportingHelper {
    private NodeService nodeService;
    private ServiceRegistry serviceRegistry;
    private NamespaceService namespaceService;

    private String blacklist = ",";
    private String invalidTableName = "";

    private Properties namespacesShortToLong = null;
    private Properties classToColumn;
    private Properties replacementTypes;
    private Properties namespaces;
    private Properties globalProperties;

    private Properties tableNameCache = new Properties();
    private NodeRef reportingRootRef = null;

    private HierarchicalResourceLoader hierarchicalResourceLoader;
    private String configLocation;

    private String vendor;
    private static Log logger = LogFactory.getLog(ReportingHelper.class);

    public Properties getTableNameCache() {
        return tableNameCache;
    }

    public void addTableNameCache(String tablename, String tableNameFixed) {
        this.tableNameCache.setProperty(tablename, tableNameFixed);
    }

    public boolean getResetDoneStatusAtStartup() {
        boolean returnBoolean = true;
        try {
            NodeRef reportingRoot = getReportingRoot();
            logger.info("getResetDoneStatusAtStartup reportingRoot=" + reportingRoot);

            returnBoolean = (Boolean) nodeService.getProperty(reportingRoot,
                    ReportingModel.PROP_REPORTING_RESET_DDNE_STATUS_AT_STARTUP);
            logger.info("getResetDoneStatusAtStartup returning: " + returnBoolean);
            //Properties globalProperties = reportingHelper.getGlobalProperties();
            //returnBoolean = "true".equals(
            //   globalProperties.getProperty(Constants.property_resetStatusAtStartupEnabled, "true").toLowerCase());
        } catch (Exception e) {
            logger.error("Error getResetDoneStatusAtStartup(): " + e);
        }
        return returnBoolean;
    }

    private NodeRef getReportingRoot() {
        NodeRef thisRootRef = null;
        if (false) {
            // none
        } else {

            ResultSet placeHolderResults = null;
            try {

                String fullQuery = "TYPE:\"reporting:reportingRoot\"";
                SearchParameters sp = new SearchParameters();
                sp.setLanguage(SearchService.LANGUAGE_LUCENE);
                sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
                sp.setQuery(fullQuery);
                placeHolderResults = serviceRegistry.getSearchService().query(sp);
                //      Constants.QUERYLANGUAGE, 
                //      "ASPECT:\"reporting:reportingRoot\"");

                // cycle the resultset of containers
                for (ResultSetRow placeHolderRow : placeHolderResults) {
                    thisRootRef = placeHolderRow.getChildAssocRef().getChildRef();

                    logger.debug("Found reporting root: "
                            + nodeService.getProperty(thisRootRef, ContentModel.PROP_NAME));

                } // end for ResultSetRow
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (placeHolderResults != null) {
                    placeHolderResults.close();
                }
            }
        }
        return thisRootRef;
    }

    /**
     * Navigate up in the folder structure a ReportingRoot is found.
     * 
     * @param currentNode
     * @return NodeRef of the first ReportingRoot the currentNode is a child of
     */
    public NodeRef getReportingRoot(final NodeRef currentNode) {
        return getParentByType(currentNode, ReportingModel.TYPE_REPORTING_ROOT);
    }

    /**
     * Navigate up in the folder structure a ReportingContainer is found.
     * 
     * @param currentNode
     * @return NodeRef of the first ReportingContainer the currentNode is a child of
     */
    public NodeRef getReportingContainer(final NodeRef currentNode) {
        return getParentByType(currentNode, ReportingModel.TYPE_REPORTING_CONTAINER);
    }

    /**
     * Navigate up in the folder structure until either the object type with the
     * given QName-type (targetType) is found, or we touch the repositoryRoot.
     * 
     * @param currentNode
     * @param targetType
     * @return NodeRef of the first parent typed with the given QName
     */
    private NodeRef getParentByType(final NodeRef currentNode, final QName targetType) {
        // consider managing a cache of noderef-to-noderef relations per QName

        logger.debug("Enter getParentByType");
        NodeRef returnNode = null;

        if (currentNode != null) {

            returnNode = AuthenticationUtil.runAs(new RunAsWork<NodeRef>() {
                public NodeRef doWork() throws Exception {

                    NodeRef rootNode = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);

                    logger.debug("getParentByType: rootNode=" + rootNode);
                    logger.debug("getParentByType: nodeRef=" + currentNode);

                    NodeRef returnNode = null;
                    NodeRef loopRef = currentNode;
                    boolean siteTypeFound = false;
                    while (!loopRef.equals(rootNode) && !siteTypeFound) {
                        //logger.debug("getTypeForNode: voor loopRef="+loopRef);
                        loopRef = nodeService.getPrimaryParent(loopRef).getParentRef();
                        //logger.debug("getTypeForNode: na   loopRef="+loopRef);
                        siteTypeFound = nodeService.getType(loopRef).equals(targetType);
                        if (siteTypeFound) {
                            returnNode = loopRef;
                            logger.debug("getParentByType: Found QName node!");
                        }
                    }
                    return returnNode;
                } // end do work
            }, AuthenticationUtil.getSystemUserName());

        } // end if nodeRef!=null
        logger.debug("Exit getParentByType: " + returnNode);
        return returnNode;
    }

    /**
     * Given the selected value in the picklist in the UI, return the JAVA
     * API string for the particular language, as defined in he SearchService
     * 
     * @param objectLanguage UI search language
     * @return JAVA API name for the language
     */
    public String getSearchLanguage(String objectLanguage) {
        String returnString = SearchService.LANGUAGE_LUCENE;
        if (objectLanguage != null) {
            if ("Full Text Search".equalsIgnoreCase(objectLanguage.trim()))
                returnString = SearchService.LANGUAGE_FTS_ALFRESCO;
            if ("Lucene".equalsIgnoreCase(objectLanguage.trim()))
                returnString = SearchService.LANGUAGE_LUCENE;
            if ("XPath".equalsIgnoreCase(objectLanguage.trim()))
                returnString = SearchService.LANGUAGE_XPATH;
        } // end if objectLanguage != null
        return returnString;
    }

    /** 
     * I cannot get these objects get created with the magic of the node service
     * on board. Therefore this method will finalize the construction of the object 
     * by pulling the content out of the Alfresco object, and setting the object props
     * 
     * @param reportingRoot
     */
    public void initializeReportingRoot(ReportingRoot reportingRoot) {
        reportingRoot.setGlobalExecutionEnabled((Boolean) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_GLOBAL_EXECUTION_ENABLED));

        reportingRoot.setHarvestEnabled((Boolean) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_HARVEST_ENABLED));

        reportingRoot.setRootQueryLanguage((String) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_ROOT_QUERY_LANGUAGE));

        reportingRoot.setOutputExtensionExcel((String) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_OUTPUTEXTENSION_EXCEL));

        reportingRoot.setOutputExtensionPdf((String) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_OUTPUTEXTENSION_PDF));

        reportingRoot.setOutputExtensionCsv((String) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_OUTPUTEXTENSION_CSV));

        reportingRoot.setTargetQueries((String) nodeService.getProperty(reportingRoot.getNodeRef(),
                ReportingModel.PROP_REPORTING_TARGET_QUERIES));

        reportingRoot.setName((String) nodeService.getProperty(reportingRoot.getNodeRef(), ContentModel.PROP_NAME));

    }

    public void initializeReportingContainer(ReportingContainer reportingContainer) {
        reportingContainer.setExecutionEnabled((Boolean) nodeService.getProperty(reportingContainer.getNodeRef(),
                ReportingModel.PROP_REPORTING_EXECUTION_ENABLED));

        reportingContainer.setExecutionFrequency((String) nodeService.getProperty(reportingContainer.getNodeRef(),
                ReportingModel.PROP_REPORTING_EXECUTION_FREQUENCY));

        reportingContainer
                .setName((String) nodeService.getProperty(reportingContainer.getNodeRef(), ContentModel.PROP_NAME));

        if (logger.isDebugEnabled()) {
            logger.debug("initializeReportingContainer:");
            logger.debug("  Setting execution enabled = " + nodeService.getProperty(reportingContainer.getNodeRef(),
                    ReportingModel.PROP_REPORTING_EXECUTION_ENABLED));
            logger.debug("  Execution frequency: " + nodeService.getProperty(reportingContainer.getNodeRef(),
                    ReportingModel.PROP_REPORTING_EXECUTION_FREQUENCY));
            logger.debug(
                    "  Name: " + nodeService.getProperty(reportingContainer.getNodeRef(), ContentModel.PROP_NAME));
        }
    }

    public void initializeReport(ReportTemplate report) {
        report.setName((String) nodeService.getProperty(report.getNodeRef(), ContentModel.PROP_NAME));

        report.setOutputFormat((String) nodeService.getProperty(report.getNodeRef(),
                ReportingModel.PROP_REPORTING_REPORTING_FORMAT));

        report.setOutputVersioned((Boolean) nodeService.getProperty(report.getNodeRef(),
                ReportingModel.PROP_REPORTING_REPORTING_VERSIONED));
        report.setReportingDocument(
                nodeService.hasAspect(report.getNodeRef(), ReportingModel.ASPECT_REPORTING_REPORTABLE));

        report.setTargetPath(
                (String) nodeService.getProperty(report.getNodeRef(), ReportingModel.PROP_REPORTING_TARGET_PATH));

        report.setSubstitution(
                (String) nodeService.getProperty(report.getNodeRef(), ReportingModel.PROP_REPORTING_SUBSTITUTION));

        List<AssociationRef> ar = nodeService.getTargetAssocs(report.getNodeRef(),
                ReportingModel.ASSOC_REPORTING_TARGET_NODE);

        if (!ar.isEmpty()) {
            report.setTargetNode(ar.get(0).getTargetRef());
        }
    }

    /**
     * returns a Properties object with shortValue = longValue QName
     * This enables the system to get a short key for a long QName
     * @return
     */
    public Properties getNameSpacesShortToLong() {
        if (this.namespacesShortToLong == null) {
            this.namespacesShortToLong = new Properties();
            Collection<String> keys = namespaceService.getPrefixes();
            for (String shortValue : keys) {
                String longValue = namespaceService.getNamespaceURI(shortValue);
                this.namespacesShortToLong.setProperty(shortValue, longValue);
                logger.debug("replaceShortQNameIntoLong: Replacing short value: " + shortValue
                        + " into long value: " + longValue);
            }
        }
        return this.namespacesShortToLong;
    }

    /**
     * Method to convert a short notation into a long notation to 
     * be able to construct a QName object from that
     * 
     * @param inString in the form cm:name or st:sitePreset
     * @return a valid QName representing the property (or type, 
     * or aspect) in the Java World
     */
    public QName replaceShortQNameIntoLong(String inString) {
        Properties namespaces = getNameSpacesShortToLong();
        String namespace = inString.split(":")[0];
        String property = inString.split(":")[1];
        String longName = namespaces.getProperty(namespace);
        logger.debug("replaceShortQNameIntoLong: Creating long QName: " + longName);
        QName longQName = QName.createQName(longName, property);
        return longQName;
    }

    /**
     * Given the input string, replace all namespaces where possible. 
     * @param namespace
     * @return string whith replaced full namespaces into short namespace definitions
     */

    public String replaceNameSpaces(String namespace) {
        // use regular expressions to do a global replace of the full namespace into the short version.
        Properties p = getNameSpaces();
        Enumeration<Object> keys = p.keys();
        while (keys.hasMoreElements()) {
            String into = (String) keys.nextElement();
            String from = p.getProperty(into);
            namespace = namespace.replace(from, into);
        }
        namespace = namespace.replace("-", "_");

        return namespace;
    }

    /**
     * Truncate the table or column name to the max length defined by the respective vendor
     * @param originalName The name of column/table you like to create
     * @return the potentially truncated name of the table of the column
     */
    public String getTableColumnNameTruncated(final String originalName) {
        final String vendor = getDatabaseProvider();

        String modifiedName = originalName;

        if (Constants.VENDOR_ORACLE.equals(vendor)) {
            if (originalName.length() > Constants.MAX_COLUMNNAME_LENGTH_ORACLE) {
                modifiedName = originalName.substring(0, Constants.MAX_COLUMNNAME_LENGTH_ORACLE);
            }
        }
        if (Constants.VENDOR_MYSQL.equals(vendor)) {
            if (originalName.length() > Constants.MAX_COLUMNNAME_LENGTH_MYSQL) {
                modifiedName = originalName.substring(0, Constants.MAX_COLUMNNAME_LENGTH_MYSQL);
            }
        }
        if (Constants.VENDOR_POSTGRES.equals(vendor)) {
            if (originalName.length() > Constants.MAX_COLUMNNAME_LENGTH_POSTGRES) {
                modifiedName = originalName.substring(0, Constants.MAX_COLUMNNAME_LENGTH_POSTGRES);
            }
        }
        return modifiedName;
    }

    /**
     * Mapping of Alfresco property TYPES onto SQL column definitions.
     * the value of "-" means the Alfresco property will NOT be automatically 
     * mapped into the SQL database. The properties file will be read from classpath
     * There are custom calls for Site, Category, Tags
     * 
     * @return Properties object
     * @throws Exception 
     */
    public Properties getClassToColumnType() throws Exception {
        if (classToColumn == null) {

            //try {
            ClassLoader cl = this.getClass().getClassLoader();

            // get the ibatis resource path from the resource loader. 
            // Tweak this to match our vendor-specific Alfresco type-to-column mapping
            // in order to facilitate differences in DATETIME, TIMESTAMP etc.
            String url = hierarchicalResourceLoader.getResourcePath();
            if (logger.isDebugEnabled()) {
                logger.debug("MyBatis resource path: " + url);
            }
            url = url.substring(0, url.lastIndexOf("/") + 1);
            url += "reporting-model.properties";
            url = "/alfresco/module/org.alfresco.reporting" + url.split("/org.alfresco.reporting")[1];
            if (logger.isDebugEnabled()) {
                logger.debug("Vendor specific mapping path: " + url);
            }

            InputStream is = cl.getResourceAsStream(url);
            Properties p = new Properties();
            p.load(is);
            classToColumn = p;
            if (logger.isInfoEnabled())
                logger.info("classToColumn Loaded!");
            //} catch (IOException e) {
            //   e.printStackTrace();
            //   throw new Exception(e);
            //}
        }

        return classToColumn;
    }

    /**
     * Reads external properties file. The properties in this file will override 
     * the default mapping of individual Alfresco PROPERTY types into SQL column  
     * definitions ** on a per-property basis**
     * (One often knows a zip code is d:text, but never more than 8 charaters, so 
     * VARCHAR(8) will do). The total length of the row (sum of the column lengths) 
     * can never be more than 65535 bytes. And i guess UTF-8 makes a reservation of 
     * 4 bytes per character
     * 
     * The properties file REPORTING_CUSTOM_PROPERTIES can be named differently using
     * the method setCustomModelProperties(String newFileName)
     * 
     * @return Properties object with as content the key/value pairs from the properties file.
     */
    public Properties getReplacementDataType() {

        try {
            ClassLoader cl = this.getClass().getClassLoader();
            InputStream is = cl.getResourceAsStream(Constants.REPORTING_CUSTOM_PROPERTIES);
            Properties p = new Properties();
            p.load(is);
            replacementTypes = p;
        } catch (Exception e) {
            //e.printStackTrace();
            replacementTypes = new Properties();
        }

        return replacementTypes;
    }

    /**
     * Get the full list of namespaces and their short form. Cache for future need.
     * @return
     */
    public Properties getNameSpaces() {
        if (this.namespaces == null) {
            this.namespaces = new Properties();
            Collection<String> keys = serviceRegistry.getNamespaceService().getPrefixes();
            for (String key : keys) {
                String value = serviceRegistry.getNamespaceService().getNamespaceURI(key);
                String into = key + "_";
                String from = "{" + value + "}";
                this.namespaces.setProperty(into, from);
                logger.debug("getNameSpaces: Replacing: " + from + " into: " + into);
            }
        }
        return this.namespaces;
    }

    public Properties getGlobalProperties() {
        if (this.globalProperties == null) {
            logger.fatal("Whoot! globalProperties object is null!!");
        }
        return this.globalProperties;
    }

    private void setBlacklist(String list) {
        this.blacklist = list;
    }

    public Properties propertyKeyToLowerCase(Properties p) {
        Properties pp = new Properties();
        Iterator<Object> pIterator = p.keySet().iterator();
        String key;
        while (pIterator.hasNext()) {
            key = (String) pIterator.next();
            pp.setProperty(key.toLowerCase(), p.getProperty(key));
        }
        return pp;
    }

    public String getBlacklist() {
        String keys = ",";
        if (",".equals(this.blacklist)) {
            keys = getGlobalProperties().getProperty(Constants.property_blockkeys, "-") + ",";
            keys += getGlobalProperties().getProperty(Constants.property_blacklist, "") + ",";
            keys = keys.replaceAll("-", "_");
            keys = keys.replaceAll(":", "_");
            setBlacklist(keys);
        } else {
            keys = this.blacklist;
        }
        return keys;
    }

    public String getDatabaseProvider() {
        if (this.vendor == null) {
            this.vendor = hierarchicalResourceLoader.getDatabaseVendor();
        }
        return this.vendor;
    }

    /**
     * Filter out most common reserved words as tablename. Next to that, filter on blanks and minus
     * because it will kill functionality. Need using different quotes for different DB vendors :-(
     * @param tableName
     * @return
     */
    public String getValidTableName(String tableName) {
        if ("".equals(invalidTableName)) {
            invalidTableName = "," + globalProperties.getProperty(Constants.property_invalid_table_names,
                    "select,from,where,group,order by,order,by,distinct") + ",";
        }
        String tableNameFixed = "";

        // check if fix for tablename already in a cache property
        if (!getTableNameCache().containsKey(tableName)) {
            // get rid of blanks, and minus. They are poison because need quotes to fix, 
            // but different quotes depending on DB vendor
            logger.debug("getValidTableName in--tableName=" + tableName);
            String replaceString = globalProperties.getProperty(Constants.property_invalid_table_chars, "- `'");
            tableNameFixed = tableName.toLowerCase().trim();
            for (int i = 0; i < replaceString.length(); i++) {
                String character = String.valueOf(replaceString.charAt(i));
                logger.debug("getValidTableName character=" + character);
                while (tableNameFixed.contains(character)) {
                    tableNameFixed = tableNameFixed.substring(0, tableNameFixed.indexOf(character)) + "_"
                            + tableNameFixed.substring(tableNameFixed.indexOf(character) + 1);
                }
            }
            logger.debug("getValidTableName out-tableName=" + tableNameFixed);

            //tableName = tableName.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_").trim();

            if (invalidTableName.toLowerCase().contains("," + tableNameFixed + ",")) {
                tableNameFixed = "_" + tableNameFixed;
            }

            // Check if length >64, the max for MySQL
            if (tableNameFixed.length() > 60) {
                tableNameFixed = tableNameFixed.substring(0, 60);
            }

            tableNameFixed = tableNameFixed.toLowerCase();
            logger.debug("getValidTableName after reserved words=" + tableNameFixed);
            addTableNameCache(tableName, tableNameFixed);
        } else {
            tableNameFixed = getTableNameCache().getProperty(tableName);
            logger.debug("getValidTableName cache hit=" + tableNameFixed);
        }

        // make sure the length of the name is within bounrdaries of the database vendor
        tableNameFixed = getTableColumnNameTruncated(tableNameFixed);

        return tableNameFixed;
    }

    public SimpleDateFormat getSimpleDateFormat() {
        String vendor = getDatabaseProvider();
        String dateformat = Constants.DATE_FORMAT_AUDIT;

        if ("mysql".equalsIgnoreCase(vendor)) {
            dateformat = Constants.DATE_FORMAT_MYSQL;
        }
        if ("postgresql".equalsIgnoreCase(vendor)) {
            dateformat = Constants.DATE_FORMAT_POSTGRESQL;
        }
        if ("oracle".equalsIgnoreCase(vendor)) {
            dateformat = Constants.DATE_FORMAT_ORACLE;
        }
        if ("sqlserver".equalsIgnoreCase(vendor)) {
            dateformat = Constants.DATE_FORMAT_MSSQL;
        }

        SimpleDateFormat sdf = new SimpleDateFormat(dateformat);// "yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getDefault());
        return sdf;
    }

    public String getSearchLanguage(NodeRef harvestDefinition) {
        String language = SearchService.LANGUAGE_LUCENE;
        if (SearchService.LANGUAGE_LUCENE.equalsIgnoreCase((String) nodeService.getProperty(harvestDefinition,
                ReportingModel.PROP_REPORTING_ROOT_QUERY_LANGUAGE))) {
            language = SearchService.LANGUAGE_LUCENE;
        }
        if (SearchService.LANGUAGE_CMIS_ALFRESCO.equalsIgnoreCase((String) nodeService
                .getProperty(harvestDefinition, ReportingModel.PROP_REPORTING_ROOT_QUERY_LANGUAGE))) {
            language = SearchService.LANGUAGE_CMIS_ALFRESCO;
        }
        return language;
    }

    /**
     * General Setter for the Alfresco NodeService
     * @param nodeService
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    public void setNamespaceService(NamespaceService namespaceService) {
        this.namespaceService = namespaceService;
    }

    public void setServiceRegistry(ServiceRegistry serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    public void setProperties(Properties properties) {
        this.globalProperties = properties;
    }

    public void setHierarchicalResourceLoader(HierarchicalResourceLoader hierarchicalResourceLoader) {
        this.hierarchicalResourceLoader = hierarchicalResourceLoader;
    }

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }
}