/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * 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.apache.ofbiz.entity.config.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.ofbiz.base.lang.ThreadSafe;
import org.apache.ofbiz.base.util.UtilXml;
import org.apache.ofbiz.entity.GenericEntityConfException;
import org.w3c.dom.Element;

/**
 * An object that models the <code>&lt;datasource&gt;</code> element.
 *
 * @see <code>entity-config.xsd</code>
 */
@ThreadSafe
public final class Datasource {

    private final String name; // type = xs:string
    private final String helperClass; // type = xs:string
    private final String fieldTypeName; // type = xs:string
    private final boolean useSchemas;
    private final String schemaName; // type = xs:string
    private final boolean checkOnStart;
    private final boolean addMissingOnStart;
    private final boolean usePkConstraintNames;
    private final boolean checkPksOnStart;
    private final int constraintNameClipLength; // type = xs:nonNegativeInteger
    private final boolean useProxyCursor;
    private final String proxyCursorName; // type = xs:string
    private final int resultFetchSize; // type = xs:integer
    private final boolean useForeignKeys;
    private final boolean useForeignKeyIndices;
    private final boolean checkFksOnStart;
    private final boolean checkFkIndicesOnStart;
    private final String fkStyle;
    private final boolean useFkInitiallyDeferred;
    private final boolean useIndices;
    private final boolean useIndicesUnique;
    private final boolean checkIndicesOnStart;
    private final String joinStyle;
    private final boolean aliasViewColumns;
    private final boolean alwaysUseConstraintKeyword;
    private final boolean dropFkUseForeignKeyKeyword;
    private final boolean useBinaryTypeForBlob;
    private final boolean useOrderByNulls;
    private final String offsetStyle;
    private final String tableType; // type = xs:string
    private final String characterSet; // type = xs:string
    private final String collate; // type = xs:string
    private final int maxWorkerPoolSize; // type = xs:integer
    private final List<SqlLoadPath> sqlLoadPathList; // <sql-load-path>
    private final List<ReadData> readDataList; // <read-data>
    private final InlineJdbc inlineJdbc; // <inline-jdbc>
    private final JndiJdbc jndiJdbc; // <jndi-jdbc>
    private final TyrexDataSource tyrexDataSource; // <tyrex-dataSource>

    Datasource(Element element) throws GenericEntityConfException {
        String lineNumberText = EntityConfig.createConfigFileLineNumberText(element);
        String name = element.getAttribute("name").intern();
        if (name.isEmpty()) {
            throw new GenericEntityConfException("<datasource> element name attribute is empty" + lineNumberText);
        }
        this.name = name;
        String helperClass = element.getAttribute("helper-class").intern();
        if (helperClass.isEmpty()) {
            throw new GenericEntityConfException("<datasource> element helper-class attribute is empty" + lineNumberText);
        }
        this.helperClass = helperClass;
        String fieldTypeName = element.getAttribute("field-type-name").intern();
        if (fieldTypeName.isEmpty()) {
            throw new GenericEntityConfException("<datasource> element field-type-name attribute is empty" + lineNumberText);
        }
        this.fieldTypeName = fieldTypeName;
        this.useSchemas = !"false".equals(element.getAttribute("use-schemas"));
        this.schemaName = element.getAttribute("schema-name").intern();
        this.checkOnStart = !"false".equals(element.getAttribute("check-on-start"));
        this.addMissingOnStart = "true".equals(element.getAttribute("add-missing-on-start"));
        this.usePkConstraintNames = !"false".equals(element.getAttribute("use-pk-constraint-names"));
        this.checkPksOnStart = !"false".equals(element.getAttribute("check-pks-on-start"));
        String constraintNameClipLength = element.getAttribute("constraint-name-clip-length");
        if (constraintNameClipLength.isEmpty()) {
            this.constraintNameClipLength = 30;
        } else {
            try {
                this.constraintNameClipLength = Integer.parseInt(constraintNameClipLength);
            } catch (Exception e) {
                throw new GenericEntityConfException("<datasource> element constraint-name-clip-length attribute is invalid" + lineNumberText);
            }
        }
        this.useProxyCursor = "true".equalsIgnoreCase(element.getAttribute("use-proxy-cursor"));
        String proxyCursorName = element.getAttribute("proxy-cursor-name").intern();
        if (proxyCursorName.isEmpty()) {
            proxyCursorName = "p_cursor";
        }
        this.proxyCursorName = proxyCursorName;
        String resultFetchSize = element.getAttribute("result-fetch-size");
        if (resultFetchSize.isEmpty()) {
            this.resultFetchSize = -1;
        } else {
            try {
                this.resultFetchSize = Integer.parseInt(resultFetchSize);
            } catch (Exception e) {
                throw new GenericEntityConfException("<datasource> element result-fetch-size attribute is invalid" + lineNumberText);
            }
        }
        this.useForeignKeys = !"false".equals(element.getAttribute("use-foreign-keys"));
        this.useForeignKeyIndices = !"false".equals(element.getAttribute("use-foreign-key-indices"));
        this.checkFksOnStart = "true".equals(element.getAttribute("check-fks-on-start"));
        this.checkFkIndicesOnStart = "true".equals(element.getAttribute("check-fk-indices-on-start"));
        String fkStyle = element.getAttribute("fk-style").intern();
        if (fkStyle.isEmpty()) {
            fkStyle = "name_constraint";
        }
        this.fkStyle = fkStyle;
        this.useFkInitiallyDeferred = "true".equals(element.getAttribute("use-fk-initially-deferred"));
        this.useIndices = !"false".equals(element.getAttribute("use-indices"));
        this.useIndicesUnique = !"false".equals(element.getAttribute("use-indices-unique"));
        this.checkIndicesOnStart = "true".equals(element.getAttribute("check-indices-on-start"));
        String joinStyle = element.getAttribute("join-style").intern();
        if (joinStyle.isEmpty()) {
            joinStyle = "ansi";
        }
        this.joinStyle = joinStyle;
        this.aliasViewColumns = "true".equals(element.getAttribute("alias-view-columns"));
        this.alwaysUseConstraintKeyword = "true".equals(element.getAttribute("always-use-constraint-keyword"));
        this.dropFkUseForeignKeyKeyword = "true".equals(element.getAttribute("drop-fk-use-foreign-key-keyword"));
        this.useBinaryTypeForBlob = "true".equals(element.getAttribute("use-binary-type-for-blob"));
        this.useOrderByNulls = "true".equals(element.getAttribute("use-order-by-nulls"));
        String offsetStyle = element.getAttribute("offset-style").intern();
        if (offsetStyle.isEmpty()) {
            offsetStyle = "none";
        }
        this.offsetStyle = offsetStyle;
        this.tableType = element.getAttribute("table-type").intern();
        this.characterSet = element.getAttribute("character-set").intern();
        this.collate = element.getAttribute("collate").intern();
        String maxWorkerPoolSize = element.getAttribute("max-worker-pool-size").intern();
        if (maxWorkerPoolSize.isEmpty()) {
            this.maxWorkerPoolSize = 1;
        } else {
            try {
                int maxWorkerPoolSizeInt = Integer.parseInt(maxWorkerPoolSize);
                if (maxWorkerPoolSizeInt == 0) {
                    maxWorkerPoolSizeInt = 1;
                } else if (maxWorkerPoolSizeInt < 0) {
                    maxWorkerPoolSizeInt = Math.abs(maxWorkerPoolSizeInt) * Runtime.getRuntime().availableProcessors();
                }
                this.maxWorkerPoolSize = maxWorkerPoolSizeInt;
            } catch (NumberFormatException e) {
                throw new GenericEntityConfException("<datasource> element max-worker-pool-size attribute is invalid" + lineNumberText);
            }
        }
        List<? extends Element> sqlLoadPathElementList = UtilXml.childElementList(element, "sql-load-path");
        if (sqlLoadPathElementList.isEmpty()) {
            this.sqlLoadPathList = Collections.emptyList();
        } else {
            List<SqlLoadPath> sqlLoadPathList = new ArrayList<>(sqlLoadPathElementList.size());
            for (Element sqlLoadPathElement : sqlLoadPathElementList) {
                sqlLoadPathList.add(new SqlLoadPath(sqlLoadPathElement));
            }
            this.sqlLoadPathList = Collections.unmodifiableList(sqlLoadPathList);
        }
        List<? extends Element> readDataElementList = UtilXml.childElementList(element, "read-data");
        if (readDataElementList.isEmpty()) {
            this.readDataList = Collections.emptyList();
        } else {
            List<ReadData> readDataList = new ArrayList<>(readDataElementList.size());
            for (Element readDataElement : readDataElementList) {
                readDataList.add(new ReadData(readDataElement));
            }
            this.readDataList = Collections.unmodifiableList(readDataList);
        }
        int jdbcElementCount = 0;
        Element inlineJdbcElement = UtilXml.firstChildElement(element, "inline-jdbc");
        if (inlineJdbcElement == null) {
            this.inlineJdbc = null;
        } else {
            this.inlineJdbc = new InlineJdbc(inlineJdbcElement);
            jdbcElementCount++;
        }
        Element jndiJdbcElement = UtilXml.firstChildElement(element, "jndi-jdbc");
        if (jndiJdbcElement == null) {
            this.jndiJdbc = null;
        } else {
            this.jndiJdbc = new JndiJdbc(jndiJdbcElement);
            jdbcElementCount++;
        }
        Element tyrexElement = UtilXml.firstChildElement(element, "tyrex-dataSource");
        if (tyrexElement == null) {
            this.tyrexDataSource = null;
        } else {
            this.tyrexDataSource = new TyrexDataSource(tyrexElement);
            jdbcElementCount++;
        }
        if (jdbcElementCount > 1) {
            throw new GenericEntityConfException("<datasource> element is invalid: Only one of <inline-jdbc>, <jndi-jdbc>, "
                    + "<tyrex-dataSource> is allowed" + lineNumberText);
        }
    }

    /** Returns the value of the <code>name</code> attribute. */
    public String getName() {
        return this.name;
    }

    /** Returns the value of the <code>helper-class</code> attribute. */
    public String getHelperClass() {
        return this.helperClass;
    }

    /** Returns the value of the <code>field-type-name</code> attribute. */
    public String getFieldTypeName() {
        return this.fieldTypeName;
    }

    /** Returns the value of the <code>use-schemas</code> attribute. */
    public boolean getUseSchemas() {
        return this.useSchemas;
    }

    /** Returns the value of the <code>schema-name</code> attribute. */
    public String getSchemaName() {
        return this.schemaName;
    }

    /** Returns the value of the <code>check-on-start</code> attribute. */
    public boolean getCheckOnStart() {
        return this.checkOnStart;
    }

    /** Returns the value of the <code>add-missing-on-start</code> attribute. */
    public boolean getAddMissingOnStart() {
        return this.addMissingOnStart;
    }

    /** Returns the value of the <code>use-pk-constraint-names</code> attribute. */
    public boolean getUsePkConstraintNames() {
        return this.usePkConstraintNames;
    }

    /** Returns the value of the <code>check-pks-on-start</code> attribute. */
    public boolean getCheckPksOnStart() {
        return this.checkPksOnStart;
    }

    /** Returns the value of the <code>constraint-name-clip-length</code> attribute. */
    public int getConstraintNameClipLength() {
        return this.constraintNameClipLength;
    }

    /** Returns the value of the <code>use-proxy-cursor</code> attribute. */
    public boolean getUseProxyCursor() {
        return this.useProxyCursor;
    }

    /** Returns the value of the <code>proxy-cursor-name</code> attribute. */
    public String getProxyCursorName() {
        return this.proxyCursorName;
    }

    /** Returns the value of the <code>result-fetch-size</code> attribute. */
    public int getResultFetchSize() {
        return this.resultFetchSize;
    }

    /** Returns the value of the <code>use-foreign-keys</code> attribute. */
    public boolean getUseForeignKeys() {
        return this.useForeignKeys;
    }

    /** Returns the value of the <code>use-foreign-key-indices</code> attribute. */
    public boolean getUseForeignKeyIndices() {
        return this.useForeignKeyIndices;
    }

    /** Returns the value of the <code>check-fks-on-start</code> attribute. */
    public boolean getCheckFksOnStart() {
        return this.checkFksOnStart;
    }

    /** Returns the value of the <code>check-fk-indices-on-start</code> attribute. */
    public boolean getCheckFkIndicesOnStart() {
        return this.checkFkIndicesOnStart;
    }

    /** Returns the value of the <code>fk-style</code> attribute. */
    public String getFkStyle() {
        return this.fkStyle;
    }

    /** Returns the value of the <code>use-fk-initially-deferred</code> attribute. */
    public boolean getUseFkInitiallyDeferred() {
        return this.useFkInitiallyDeferred;
    }

    /** Returns the value of the <code>use-indices</code> attribute. */
    public boolean getUseIndices() {
        return this.useIndices;
    }

    /** Returns the value of the <code>use-indices-unique</code> attribute. */
    public boolean getUseIndicesUnique() {
        return this.useIndicesUnique;
    }

    /** Returns the value of the <code>check-indices-on-start</code> attribute. */
    public boolean getCheckIndicesOnStart() {
        return this.checkIndicesOnStart;
    }

    /** Returns the value of the <code>join-style</code> attribute. */
    public String getJoinStyle() {
        return this.joinStyle;
    }

    /** Returns the value of the <code>alias-view-columns</code> attribute. */
    public boolean getAliasViewColumns() {
        return this.aliasViewColumns;
    }

    /** Returns the value of the <code>always-use-constraint-keyword</code> attribute. */
    public boolean getAlwaysUseConstraintKeyword() {
        return this.alwaysUseConstraintKeyword;
    }

    /** Returns the value of the <code>drop-fk-use-foreign-key-keyword</code> attribute. */
    public boolean getDropFkUseForeignKeyKeyword() {
        return this.dropFkUseForeignKeyKeyword;
    }

    /** Returns the value of the <code>use-binary-type-for-blob</code> attribute. */
    public boolean getUseBinaryTypeForBlob() {
        return this.useBinaryTypeForBlob;
    }

    /** Returns the value of the <code>use-order-by-nulls</code> attribute. */
    public boolean getUseOrderByNulls() {
        return this.useOrderByNulls;
    }

    /** Returns the value of the <code>offset-style</code> attribute. */
    public String getOffsetStyle() {
        return this.offsetStyle;
    }

    /** Returns the value of the <code>table-type</code> attribute. */
    public String getTableType() {
        return this.tableType;
    }

    /** Returns the value of the <code>character-set</code> attribute. */
    public String getCharacterSet() {
        return this.characterSet;
    }

    /** Returns the value of the <code>collate</code> attribute. */
    public String getCollate() {
        return this.collate;
    }

    /** Returns the value of the <code>max-worker-pool-size</code> attribute. */
    public int getMaxWorkerPoolSize() {
        return this.maxWorkerPoolSize;
    }

    /** Returns the <code>&lt;sql-load-path&gt;</code> child elements. */
    public List<SqlLoadPath> getSqlLoadPathList() {
        return this.sqlLoadPathList;
    }

    /** Returns the <code>&lt;read-data&gt;</code> child elements. */
    public List<ReadData> getReadDataList() {
        return this.readDataList;
    }

    /** Returns the <code>&lt;inline-jdbc&gt;</code> child element. */
    public InlineJdbc getInlineJdbc() {
        return this.inlineJdbc;
    }

    /** Returns the <code>&lt;jndi-jdbc&gt;</code> child element. */
    public JndiJdbc getJndiJdbc() {
        return this.jndiJdbc;
    }

    /** Returns the <code>&lt;tyrex-dataSource&gt;</code> child element. */
    public TyrexDataSource getTyrexDataSource() {
        return this.tyrexDataSource;
    }
}
