From 85d4a372425bbe5b7b3d4bc3adf8bac8fe1fa94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Thu, 26 Dec 2024 09:42:48 +0100 Subject: [PATCH] [BASE-93] Merge ConnId Commons back --- java/commons/db/pom.xml | 79 ++ .../tirasa/connid/commons/db/Constants.java | 54 + .../tirasa/connid/commons/db/DBMessages.java | 36 + .../connid/commons/db/DatabaseConnection.java | 203 ++++ .../commons/db/DatabaseFilterTranslator.java | 290 +++++ .../commons/db/DatabaseQueryBuilder.java | 257 +++++ .../tirasa/connid/commons/db/ExpectProxy.java | 129 +++ .../connid/commons/db/FilterWhereBuilder.java | 141 +++ .../connid/commons/db/InsertIntoBuilder.java | 91 ++ .../tirasa/connid/commons/db/JNDIUtil.java | 87 ++ .../connid/commons/db/LocalizedAssert.java | 137 +++ .../connid/commons/db/OperationBuilder.java | 29 + .../connid/commons/db/PropertiesResolver.java | 160 +++ .../tirasa/connid/commons/db/SQLParam.java | 247 +++++ .../net/tirasa/connid/commons/db/SQLUtil.java | 997 ++++++++++++++++++ .../connid/commons/db/UpdateSetBuilder.java | 95 ++ .../bundles/db/common/Messages.properties | 28 + .../bundles/db/common/Messages_de.properties | 28 + .../bundles/db/common/Messages_es.properties | 28 + .../bundles/db/common/Messages_fr.properties | 28 + .../bundles/db/common/Messages_it.properties | 28 + .../bundles/db/common/Messages_ja.properties | 28 + .../bundles/db/common/Messages_ko.properties | 28 + .../bundles/db/common/Messages_pt.properties | 28 + .../bundles/db/common/Messages_zh.properties | 28 + .../db/common/Messages_zh_TW.properties | 28 + .../commons/db/DatabaseConnectionTests.java | 235 +++++ .../db/DatabaseFilterTranslatorTests.java | 250 +++++ .../commons/db/DatabaseQueryBuilderTest.java | 212 ++++ .../commons/db/FilterWhereBuilderTest.java | 150 +++ .../commons/db/InsertIntoBuilderTest.java | 93 ++ .../connid/commons/db/JNDIUtilTest.java | 89 ++ .../commons/db/LocalizedAssertTest.java | 176 ++++ .../commons/db/PropertiesResolverTest.java | 97 ++ .../connid/commons/db/SQLParamTests.java | 90 ++ .../connid/commons/db/SQLUtilTests.java | 514 +++++++++ .../commons/db/UpdateSetBuilderTest.java | 102 ++ java/commons/pom.xml | 48 + java/commons/scripted/pom.xml | 79 ++ .../AbstractScriptedConfiguration.java | 396 +++++++ .../scripted/AbstractScriptedConnector.java | 898 ++++++++++++++++ .../connid/commons/scripted/Constants.java | 40 + .../commons/scripted/Messages.properties | 28 + java/pom.xml | 1 + 44 files changed, 6810 insertions(+) create mode 100644 java/commons/db/pom.xml create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/Constants.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/DBMessages.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseConnection.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseFilterTranslator.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseQueryBuilder.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/ExpectProxy.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/FilterWhereBuilder.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/InsertIntoBuilder.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/JNDIUtil.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/LocalizedAssert.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/OperationBuilder.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/PropertiesResolver.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLParam.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLUtil.java create mode 100644 java/commons/db/src/main/java/net/tirasa/connid/commons/db/UpdateSetBuilder.java create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_de.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_es.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_fr.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_it.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ja.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ko.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_pt.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh.properties create mode 100644 java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh_TW.properties create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseConnectionTests.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseFilterTranslatorTests.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseQueryBuilderTest.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/FilterWhereBuilderTest.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/InsertIntoBuilderTest.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/JNDIUtilTest.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/LocalizedAssertTest.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/PropertiesResolverTest.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLParamTests.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLUtilTests.java create mode 100644 java/commons/db/src/test/java/net/tirasa/connid/commons/db/UpdateSetBuilderTest.java create mode 100644 java/commons/pom.xml create mode 100644 java/commons/scripted/pom.xml create mode 100644 java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConfiguration.java create mode 100644 java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConnector.java create mode 100644 java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/Constants.java create mode 100644 java/commons/scripted/src/main/resources/net/tirasa/connid/commons/scripted/Messages.properties diff --git a/java/commons/db/pom.xml b/java/commons/db/pom.xml new file mode 100644 index 00000000..e4490314 --- /dev/null +++ b/java/commons/db/pom.xml @@ -0,0 +1,79 @@ + + + + + 4.0.0 + + + net.tirasa.connid + connid + 1.6.0.0-SNAPSHOT + + + commons-db + ConnId: Commons DB + + jar + + + ${basedir}/../.. + + + + + net.tirasa.connid + connector-framework + ${project.version} + + + net.tirasa.connid + connector-framework-internal + ${project.version} + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + src/main/resources + + + ${parent.path} + META-INF + + LICENSE + + + + + + diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/Constants.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/Constants.java new file mode 100644 index 00000000..e46022e8 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/Constants.java @@ -0,0 +1,54 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Tirasa. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +package net.tirasa.connid.commons.db; + +public final class Constants { + + public static final String MSG_JDBC_TEMPLATE_BLANK = "jdbc.template.blank"; + + public static final String MSG_USER_BLANK = "admin.user.blank"; + + public static final String MSG_PASSWORD_BLANK = "admin.password.blank"; + + public static final String MSG_HOST_BLANK = "host.blank"; + + public static final String MSG_PORT_BLANK = "port.blank"; + + public static final String MSG_DATABASE_BLANK = "database.blank"; + + public static final String MSG_JDBC_DRIVER_BLANK = "jdbc.driver.blank"; + + public static final String MSG_JDBC_DRIVER_NOT_FOUND = "jdbc.driver.not.found"; + + public static final String MSG_ACCOUNT_OBJECT_CLASS_REQUIRED = "acount.object.class.required"; + + public static final String MSG_INVALID_ATTRIBUTE_SET = "invalid.attribute.set"; + + public static final String MSG_UID_BLANK = "uid.blank"; + + public static final String MSG_RESULT_HANDLER_NULL = "result.handler.null"; + + private Constants() { + // private constructor for static utility class + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DBMessages.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DBMessages.java new file mode 100644 index 00000000..3fd0ba92 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DBMessages.java @@ -0,0 +1,36 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +abstract class DBMessages { + + static final String ASSERT_NULL = "assert.null"; + + static final String ASSERT_NOT_NULL = "assert.notNull"; + + static final String ASSERT_BLANK = "assert.blank"; + + static final String ASSERT_NOT_BLANK = "assert.notBlank"; + +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseConnection.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseConnection.java new file mode 100644 index 00000000..2c098f72 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseConnection.java @@ -0,0 +1,203 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import org.identityconnectors.common.logging.Log; +import org.identityconnectors.framework.common.exceptions.ConnectorException; +import org.identityconnectors.framework.spi.Configuration; + +/** + * The DatabaseConnection wraps the JDBC connection. + * + * Define the test method meaning the wrapped connection is still valid + * + * Defines come useful method to work with prepared statements * + */ +public class DatabaseConnection { + + /** + * Setup logging for the {@link DatabaseConnection}. + */ + private static final Log LOG = Log.getLog(DatabaseConnection.class); + + /** + * Internal JDBC connection. + */ + private Connection nativeConn = null; + + /** + * + * Test constructor * + */ + DatabaseConnection() { + //No body + } + + /** + * Use the {@link Configuration} passed in to immediately connect to a database. If the {@link Connection} fails a + * {@link RuntimeException} will be thrown. + * + * @param conn a connection + * @throws RuntimeException if there is a problem creating a {@link java.sql.Connection}. + */ + public DatabaseConnection(final Connection conn) { + this.nativeConn = conn; + } + + /** + * Closes the internal {@link java.sql.Connection}. + */ + public void dispose() { + SQLUtil.closeQuietly(nativeConn); + } + + /** + * Determines if the underlying JDBC {@link java.sql.Connection} is valid. + * + * @throws RuntimeException if the underlying JDBC {@link java.sql.Connection} is not valid otherwise do nothing. + */ + public void test() { + try { + // setAutoCommit() requires a connection to the server + // in most cases. But some drivers may cache the autoCommit + // value and only connect to the server if the value changes. + // (namely DB2). So we have to actually change the value twice + // and then set it back to the original value if the connection + // is still valid. setAutoCommit() is very quick so 2 round + // trips shouldn't be that bad. + // This has the BAD side effect of actually causing preceding + // partial transactions to be committed at this point. Also, + // PostgreSQL apparently caches BOTH operations, so this still + // does not work against that DB. + getConnection().setAutoCommit(!getConnection().getAutoCommit()); + getConnection().setAutoCommit(!getConnection().getAutoCommit()); + commit(); + LOG.ok("connection tested"); + } catch (Exception e) { + // anything, not just SQLException + // if the connection is not valid anymore, + // a new one will be created, so there is no + // need to set auto commit back to its original value + SQLUtil.rollbackQuietly(getConnection()); + throw ConnectorException.wrap(e); + } + } + + /** + * Get the internal JDBC connection. + * + * @return the connection + */ + public Connection getConnection() { + return this.nativeConn; + } + + /** + * Set the internal JDBC connection. + * + * @param connection new connection + */ + public void setConnection(final Connection connection) { + this.nativeConn = connection; + } + + /** + * Indirect call of prepare statement with mapped prepare statement parameters + * + * @param sql a String sql statement definition + * @param params the bind parameter values + * @return return a prepared statement + * @throws SQLException an exception in statement + */ + public PreparedStatement prepareStatement(final String sql, final List params) throws SQLException { + LOG.ok("prepareStatement: statement {0}", sql); + final List out = new ArrayList(); + final String nomalized = SQLUtil.normalizeNullValues(sql, params, out); + final PreparedStatement prepareStatement = getConnection().prepareStatement(nomalized); + SQLUtil.setParams(prepareStatement, out); + LOG.ok("prepareStatement: normalizzed statement {0} prepared", nomalized); + return prepareStatement; + } + + /** + * + * Indirect call of prepare statement using the query builder object + * + * @param query DatabaseQueryBuilder query + * + * @return return a prepared statement + * + * @throws SQLException an exception in statement + * + */ + public PreparedStatement prepareStatement(final DatabaseQueryBuilder query) throws SQLException { + final String sql = query.getSQL(); + LOG.ok("prepareStatement {0}", sql); + return prepareStatement(sql, query.getParams()); + } + + /** + * + * Indirect call of prepareCall statement with mapped callable statement parameters + * + * @param sql a String sql statement definition + * + * @param params the bind parameter values + * + * @return return a callable statement + * + * @throws SQLException an exception in statement + * + */ + public CallableStatement prepareCall(final String sql, final List params) throws SQLException { + LOG.ok("normalize call statement {0}", sql); + final List out = new ArrayList(); + final String nomalized = SQLUtil.normalizeNullValues(sql, params, out); + final CallableStatement prepareCall = getConnection().prepareCall(nomalized); + SQLUtil.setParams(prepareCall, out); + LOG.ok("call statement {0} normalizead and prepared", nomalized); + return prepareCall; + } + + /** + * + * commit transaction + * + */ + public void commit() { + try { + getConnection().commit(); + } catch (SQLException e) { + SQLUtil.rollbackQuietly(getConnection()); + LOG.error(e, "error in commit"); + throw ConnectorException.wrap(e); + } + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseFilterTranslator.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseFilterTranslator.java new file mode 100644 index 00000000..efd298c1 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseFilterTranslator.java @@ -0,0 +1,290 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.common.objects.filter.AbstractFilterTranslator; +import org.identityconnectors.framework.common.objects.filter.ContainsFilter; +import org.identityconnectors.framework.common.objects.filter.EndsWithFilter; +import org.identityconnectors.framework.common.objects.filter.EqualsFilter; +import org.identityconnectors.framework.common.objects.filter.EqualsIgnoreCaseFilter; +import org.identityconnectors.framework.common.objects.filter.GreaterThanFilter; +import org.identityconnectors.framework.common.objects.filter.GreaterThanOrEqualFilter; +import org.identityconnectors.framework.common.objects.filter.LessThanFilter; +import org.identityconnectors.framework.common.objects.filter.LessThanOrEqualFilter; +import org.identityconnectors.framework.common.objects.filter.StartsWithFilter; + +/** + * DatabaseFilterTranslator abstract class translate filters to database WHERE clause + * The resource specific getAttributeName must be provided in real translator + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public abstract class DatabaseFilterTranslator extends AbstractFilterTranslator { + + ObjectClass oclass; + + OperationOptions options; + + /** + * DatabaseFilterTranslator translate filters to database WHERE clause + * + * @param oclass the object class + * @param options the filter options + */ + public DatabaseFilterTranslator(ObjectClass oclass, OperationOptions options) { + this.oclass = oclass; + this.options = options; + } + + protected FilterWhereBuilder createBuilder() { + return new FilterWhereBuilder(); + } + + @Override + protected FilterWhereBuilder createAndExpression(FilterWhereBuilder leftExpression, + FilterWhereBuilder rightExpression) { + FilterWhereBuilder build = createBuilder(); + build.join("AND", leftExpression, rightExpression); + return build; + } + + @Override + protected FilterWhereBuilder createOrExpression(FilterWhereBuilder leftExpression, + FilterWhereBuilder rightExpression) { + FilterWhereBuilder build = createBuilder(); + build.join("OR", leftExpression, rightExpression); + return build; + } + + @Override + protected FilterWhereBuilder createEqualsExpression(EqualsFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null) { + return null; + } + final FilterWhereBuilder ret = createBuilder(); + if (not) { + ret.getWhere().append("NOT "); + } + // Normalize NULLs + if (param.getValue() == null) { + ret.addNull(param.getName()); + return ret; + } + ret.addBind(param, "=", false); + return ret; + } + + @Override + protected FilterWhereBuilder createEqualsIgnoreCaseExpression(EqualsIgnoreCaseFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null) { + return null; + } + final FilterWhereBuilder ret = createBuilder(); + if (not) { + ret.getWhere().append("NOT "); + } + // Normalize NULLs + if (param.getValue() == null) { + ret.addNull(param.getName()); + return ret; + } + ret.addBind(param, "=", true); + return ret; + } + + @Override + protected FilterWhereBuilder createContainsExpression(ContainsFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null || !(param.getValue() instanceof String)) { + //Null value filter is not supported + return null; + } + String value = (String) param.getValue(); + final FilterWhereBuilder ret = createBuilder(); + if (not) { + ret.getWhere().append("NOT "); + } + //To be sure, this is not already quoted + if (!value.startsWith("%")) { + value = "%" + value; + } + if (!value.endsWith("%")) { + value = value + "%"; + } + ret.addBind(new SQLParam(param.getName(), value, param.getSqlType()), "LIKE", false); + return ret; + } + + @Override + protected FilterWhereBuilder createEndsWithExpression(EndsWithFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null || !(param.getValue() instanceof String)) { + //Null value filter is not supported + return null; + } + String value = (String) param.getValue(); + final FilterWhereBuilder ret = createBuilder(); + if (not) { + ret.getWhere().append("NOT "); + } + //To be sure, this is not already quoted + if (!value.startsWith("%")) { + value = "%" + value; + } + ret.addBind(new SQLParam(param.getName(), value, param.getSqlType()), "LIKE", false); + return ret; + } + + @Override + protected FilterWhereBuilder createStartsWithExpression(StartsWithFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null || !(param.getValue() instanceof String)) { + //Null value filter is not supported + return null; + } + String value = (String) param.getValue(); + final FilterWhereBuilder ret = createBuilder(); + if (not) { + ret.getWhere().append("NOT "); + } + //To be sure, this is not already quoted + if (!value.endsWith("%")) { + value = value + "%"; + } + ret.addBind(new SQLParam(param.getName(), value, param.getSqlType()), "LIKE", false); + return ret; + } + + @Override + protected FilterWhereBuilder createGreaterThanExpression(GreaterThanFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null) { + return null; + } + final FilterWhereBuilder ret = createBuilder(); + final String op = not ? "<=" : ">"; + ret.addBind(param, op, false); + return ret; + } + + @Override + protected FilterWhereBuilder createGreaterThanOrEqualExpression(GreaterThanOrEqualFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null) { + return null; + } + final FilterWhereBuilder ret = createBuilder(); + final String op = not ? "<" : ">="; + ret.addBind(param, op, false); + return ret; + } + + @Override + protected FilterWhereBuilder createLessThanExpression(LessThanFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null) { + return null; + } + final FilterWhereBuilder ret = createBuilder(); + final String op = not ? ">=" : "<"; + ret.addBind(param, op, false); + return ret; + } + + @Override + protected FilterWhereBuilder createLessThanOrEqualExpression(LessThanOrEqualFilter filter, boolean not) { + final Attribute attribute = filter.getAttribute(); + if (!validateSearchAttribute(attribute)) { + return null; + } + SQLParam param = getSQLParam(attribute, oclass, options); + if (param == null || param.getValue() == null) { + return null; + } + final FilterWhereBuilder ret = createBuilder(); + final String op = not ? ">" : "<="; + ret.addBind(param, op, false); + return ret; + } + + /** + * Get the SQLParam for given attribute + * + * @param attribute to translate + * @param oclass object class + * @param options operation options + * @return the expected SQLParam, or null if filter not supported {@link java.sql.Types} + */ + protected abstract SQLParam getSQLParam(Attribute attribute, ObjectClass oclass, OperationOptions options); + + /** + * Validate the attribute to supported search types + * + * @param attribute attribute + * @return wheter attribute is valid + */ + protected boolean validateSearchAttribute(final Attribute attribute) { + //Ignore streamed ( byte[] objects ) from query, otherwise let the database process + return !byte[].class.equals(AttributeUtil.getSingleValue(attribute).getClass()); + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseQueryBuilder.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseQueryBuilder.java new file mode 100644 index 00000000..dab791a0 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/DatabaseQueryBuilder.java @@ -0,0 +1,257 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.identityconnectors.common.Pair; +import org.identityconnectors.common.StringUtil; + +/** + * The Database Query builder creates the database query. + *

+ * The main functionality of this helper class is create SQL query statement + * with bundled object references

+ * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class DatabaseQueryBuilder { + + private String selectFrom = null; //Mandatory selectFrom clause + + private String tableName = null; //Mandatory selectFrom clause + + private FilterWhereBuilder where = null; + + private Set columns = new HashSet(); + + private List orderBy = null; + + /** + * Set the columnNames to get + * + * @param columns the required columns in SQL query + */ + public void setColumns(Set columns) { + this.columns = columns; + } + + /** + * Set selectFrom and from clause + * + * @param selectFrom the selectFrom part including the from table + */ + public void setSelectFrom(String selectFrom) { + this.selectFrom = selectFrom; + } + + /** + * Set the table name + * + * @param tableName name of the table + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * set the where builder + * + * @param whereBuilder {@link FilterWhereBuilder} the where filer builder + */ + public void setWhere(FilterWhereBuilder whereBuilder) { + this.where = whereBuilder; + } + + /** + * sET THE ORDER BY CLAUSE + * + * @param orderBy a list of {@link Pair} pair as colunName and sort order + */ + public void setOrderBy(List orderBy) { + this.orderBy = orderBy; + } + + /** + * DatabaseQuery Constructor, construct selectFrom from table name, columns and where clause + * + * @param tableName The name of the database table to selectFrom from + * @param columns the names of the column to be in the result + */ + public DatabaseQueryBuilder(String tableName, Set columns) { + if (StringUtil.isBlank(tableName)) { + throw new IllegalArgumentException("the tableName must not be null or empty"); + } + if (columns == null || columns.isEmpty()) { + throw new IllegalArgumentException("CoulmnNamesToGet must not be empty"); + } + this.tableName = tableName; + this.columns = columns; + } + + /** + * DatabaseQuery Constructor which takes advantage of prepared selectFrom SQL clause + * + * @param selectFrom mandatory selectFrom clause + */ + public DatabaseQueryBuilder(final String selectFrom) { + if (StringUtil.isBlank(selectFrom)) { + throw new IllegalArgumentException("the selectFrom clause must not be empty"); + } + this.selectFrom = selectFrom; + } + + /** + * Return full sql statement string + * + * @return Sql query statement to execute + */ + public String getSQL() { + if (StringUtil.isBlank(selectFrom)) { + if (StringUtil.isBlank(tableName)) { + throw new IllegalArgumentException("the tableName must not be null or empty"); + } + if (columns != null) { + selectFrom = createSelect(columns); + } + if (StringUtil.isBlank(selectFrom)) { + throw new IllegalArgumentException("the selectFrom clause must not be empty"); + } + } else { + if (!selectFrom.toUpperCase().contains("SELECT")) { + throw new IllegalArgumentException("the required SELECt clause is missing"); + } + if (!selectFrom.toUpperCase().contains("FROM")) { + throw new IllegalArgumentException("the required FROM clause is missing"); + } + } + String ret = selectFrom; + if (where != null) { + final String whereSql = where.getWhereClause(); + if (!StringUtil.isBlank(whereSql)) { + ret = whereAnd(selectFrom, whereSql); + } + } + if (this.orderBy != null) { + StringBuilder obld = new StringBuilder(" ORDER BY "); + boolean first = true; + for (OrderBy ord : orderBy) { + if (!first) { + obld.append(", "); + } + first = false; + obld.append(ord.getColumnName()); + obld.append(ord.isAscendent() ? " ASC" : " DESC"); + } + if (obld.length() != 0) { + ret += obld.toString(); + } + } + return ret; + } + + /** + * @param sqlSelect + * @param whereAnd + * @return + */ + private String whereAnd(String sqlSelect, String whereAnd) { + int iofw = sqlSelect.indexOf("WHERE"); + return (iofw == -1) ? sqlSelect + " WHERE " + whereAnd : sqlSelect.substring(0, iofw) + "WHERE (" + sqlSelect. + substring(iofw + 5) + ") AND ( " + whereAnd + " )"; + } + + /** + * @param columns + * @param columnQuote + * @return the selectFrom statement + */ + private String createSelect(final Set columnNamesToGet) { + if (columnNamesToGet.isEmpty()) { + throw new IllegalStateException("No coulmnNamesToGet"); + } + StringBuilder ret = new StringBuilder("SELECT "); + boolean first = true; + for (String name : columnNamesToGet) { + if (!first) { + ret.append(", "); + } + ret.append(name); + ret.append(" "); + first = false; + } + ret.append("FROM "); + ret.append(tableName); + return ret.toString(); + } + + /** + * Values in wrapped object + * + * @return the where values + */ + public List getParams() { + if (where == null) { + return new ArrayList(); + } + return where.getParams(); + } + + /** + * The Required order by data subclass + */ + public static class OrderBy extends Pair { + + /** + * One order by column + * + * @param columnName column name + * @param asc true/false for ascendent/descendent + */ + public OrderBy(String columnName, Boolean asc) { + super(columnName, asc); + } + + /** + * The column name + * + * @return a name + */ + public String getColumnName() { + return this.first; + } + + /** + * The ascendent flag + * + * @return a boolean true/false as ascendent/descendent + */ + public boolean isAscendent() { + return this.second; + } + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/ExpectProxy.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/ExpectProxy.java new file mode 100644 index 00000000..db214fdd --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/ExpectProxy.java @@ -0,0 +1,129 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +/** + * This is a Test helper class for testing expected method calls and return values of interfaces + *

+ * Limitation:

+ * First implementation supports just a method name checking

+ * + * @version $Revision 1.0$ + * @param Type of the interface for testing + * @since 1.0 + */ +public class ExpectProxy implements InvocationHandler { + + private final List methodNames = new ArrayList(); + + private final List retVals = new ArrayList(); + + private int count = 0; + + /** + * Program the expected function call + * + * @param methodName the expected method name + * @param retVal the expected return value or proxy + * @return the proxy + */ + public ExpectProxy expectAndReturn( + final String methodName, final Object retVal) { + this.methodNames.add(methodName); + this.retVals.add(retVal); + return this; + } + + /** + * Program the expected method call + * + * @param methodName the expected method name + * @return the proxy + */ + public ExpectProxy expect(final String methodName) { + this.methodNames.add(methodName); + //retVals must have same number of values as methodNames + this.retVals.add(null); + return this; + } + + /** + * Program the expected method call + * + * @param methodName the expected method name + * @param throwEx the expected exception + * @return the proxy + */ + public ExpectProxy expectAndThrow( + final String methodName, final Throwable throwEx) { + return this.expectAndReturn(methodName, throwEx); + } + + /** + * Test that all expected was called in the order + * + * @return true/false all was called + */ + public boolean isDone() { + return count == methodNames.size(); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String ext = ""; + if (methodNames.size() > count) { + final String mname = this.methodNames.get(count); + ext = " The expected call no: " + (count + 1) + " was " + mname + "."; + if (method.getName().equals(mname)) { + final Object ret = retVals.get(count++); + if (ret instanceof Throwable) { + throw (Throwable) ret; + } + return ret; + } + } + throw new AssertionError( + "The call of method :" + method + " was not expected." + ext + + " Please call expectAndReturn(methodName,retVal) to fix it"); + } + + /** + * Return the Proxy implementation of the Interface + * + * @param clazz of the interface + * @return the proxy + */ + @SuppressWarnings("unchecked") + public T getProxy(Class clazz) { + ClassLoader cl = getClass().getClassLoader(); + Class intef[] = new Class[] { clazz }; + return (T) Proxy.newProxyInstance(cl, intef, this); + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/FilterWhereBuilder.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/FilterWhereBuilder.java new file mode 100644 index 00000000..5980bc1e --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/FilterWhereBuilder.java @@ -0,0 +1,141 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.util.ArrayList; +import java.util.List; +import org.identityconnectors.common.CollectionUtil; + +/** + * The Filter Where builder is component intended to be used within subclass of * + * AbstractFilterTranslator to help create the database WHERE query clause. + *

+ * The main functionality of this helper class is create SQL WHERE query clause

+ *

+ * The builder can return a List of params to be used within preparedStatement creation

+ * + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class FilterWhereBuilder { + + private boolean in; + + private final List params = new ArrayList(); + + private final StringBuilder where = new StringBuilder(); + + /** + * Compound join operator + * + * @param operator AND/OR + * @param l left FilterQueryBuiler + * @param r right FilterQueryBuiler + */ + public void join(final String operator, final FilterWhereBuilder l, final FilterWhereBuilder r) { + this.in = true; + if (l.isIn()) { + where.append("( "); + } + where.append(l.getWhere()); + if (l.isIn()) { + where.append(" )"); + } + where.append(" "); + where.append(operator); + where.append(" "); + if (r.isIn()) { + where.append("( "); + } + where.append(r.getWhere()); + if (r.isIn()) { + where.append(" )"); + } + // The params + params.addAll(l.getParams()); + params.addAll(r.getParams()); + } + + /** + * @return the params + */ + public List getParams() { + return CollectionUtil.asReadOnlyList(params); + } + + /** + * @return the where + */ + public StringBuilder getWhere() { + return where; + } + + /** + * Add name value pair bindings with operator, this is lazy bindings resolved at {@link #getWhereClause()} + * + * @see FilterWhereBuilder#getWhereClause() + * + * @param param value to builder + * @param operator an operator to compare + * @param lowercase whether case-insensitive comparison should be performed + */ + public void addBind(final SQLParam param, final String operator, final boolean lowercase) { + if (param == null) { + throw new IllegalArgumentException("null.param.not.suported"); + } + where.append(lowercase ? "LOWER(" : "").append(param.getQuotedName()).append(lowercase ? ")" : ""); + where.append(" ").append(operator). + append(lowercase ? " LOWER(" : "").append(" ?").append(lowercase ? " )" : ""); + params.add(param); + } + + /** + * Add null value. + * + * @see FilterWhereBuilder#getWhereClause() + * + * @param name of the column + */ + public void addNull(final String name) { + where.append(name); + where.append(" IS NULL"); + } + + /** + * There is a need to put the content into brackets + * + * @return boolean a in + */ + public boolean isIn() { + return in; + } + + /** + * @return the where clause as a String + */ + public String getWhereClause() { + return this.getWhere().toString(); + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/InsertIntoBuilder.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/InsertIntoBuilder.java new file mode 100644 index 00000000..cf3d48bf --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/InsertIntoBuilder.java @@ -0,0 +1,91 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.util.ArrayList; +import java.util.List; +import org.identityconnectors.common.CollectionUtil; + +/** + * The update set builder create the database update statement. + *

+ * The main functionality is create set part of update statement from Attribute set

+ * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class InsertIntoBuilder extends OperationBuilder { + + private final List params = new ArrayList(); + + private final StringBuilder into = new StringBuilder(); + + private final StringBuilder values = new StringBuilder(); + + private boolean first = true; + + /** + * Add column name and value pair + * + * @param param parameter + * @return self, builder pattern + */ + @Override + public InsertIntoBuilder addBind(final SQLParam param) { + if (!first) { + into.append(", "); + values.append(", "); + } + into.append(param.getName()); + values.append("?"); + params.add(param); + first = false; + return this; + } + + /** + * Build the into + * + * @return The SQL part + */ + public String getInto() { + return into.toString(); + } + + /** + * Build the values + * + * @return The SQL part + */ + public String getValues() { + return values.toString(); + } + + /** + * @return the param values + */ + public List getParams() { + return CollectionUtil.asReadOnlyList(params); + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/JNDIUtil.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/JNDIUtil.java new file mode 100644 index 00000000..f2d8ee1f --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/JNDIUtil.java @@ -0,0 +1,87 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.util.Properties; +import org.identityconnectors.common.StringUtil; +import org.identityconnectors.framework.common.objects.ConnectorMessages; + +/** + * Common utility methods regarding JNDI. + */ +public abstract class JNDIUtil { + + private JNDIUtil() { + //empty + } + + public static final String INVALID_JNDI_ENTRY = "invalid.jndi.entry"; + + /** + * Parses arrays of string as entries of properties. Each entry must be in form + * + * key=value. We use = as only separator and treat only first occurrence of =. + * + * Blank entries are skipped. + * + * @param entries could be null or size = 0 + * + * @param messages the error messages from the configuration resource bundle + * + * @return properties of given entries + * + * @throws IllegalArgumentException when there is any error in format of any entry + */ + public static Properties arrayToProperties(final String[] entries, final ConnectorMessages messages) { + Properties result = new Properties(); + if (entries != null) { + for (String entry : entries) { + if (StringUtil.isNotBlank(entry)) { + int firstEq = entry.indexOf('='); + if (firstEq == -1) { + throwFormatException(messages, entry, "Invalid value in JNDI entry"); + } + if (firstEq == 0) { + throwFormatException(messages, entry, "First character cannot be ="); + } + final String key = entry.substring(0, firstEq); + final String value = firstEq == entry.length() - 1 ? null : entry.substring(firstEq + 1); + result.put(key, value); + } + } + } + return result; + } + + private static void throwFormatException( + final ConnectorMessages messages, final String entry, final String defaultMsg) { + String msg; + if (messages == null) { + msg = defaultMsg + " : " + entry; + } else { + msg = messages.format(INVALID_JNDI_ENTRY, INVALID_JNDI_ENTRY + " : " + entry, entry); + } + throw new IllegalArgumentException(msg); + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/LocalizedAssert.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/LocalizedAssert.java new file mode 100644 index 00000000..a53239e1 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/LocalizedAssert.java @@ -0,0 +1,137 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import org.identityconnectors.framework.common.objects.ConnectorMessages; + +/** + * Localized asserts is a set of localized asserts utility method that throws localized + * exception when assert is not true. + * Argument names passed into assert methods can also be localized + * + * @author kitko + * + */ +public class LocalizedAssert { + + private ConnectorMessages cm; + + private boolean localizeArguments; + + /** + * Creates asserts with messages + * + * @param cm connector messages + * @throws IllegalArgumentException if cm param is null + */ + public LocalizedAssert(ConnectorMessages cm) { + if (cm == null) { + throw new IllegalArgumentException("ConnectorMessages argument is null"); + } + this.cm = cm; + } + + /** + * Creates asserts with messages with flag whether to localize argument names + * + * @param cm connector messages + * @param localizeArguments the arg + * @throws IllegalArgumentException if cm param is null + */ + public LocalizedAssert(ConnectorMessages cm, boolean localizeArguments) { + if (cm == null) { + throw new IllegalArgumentException("ConnectorMessages argument is null"); + } + this.cm = cm; + this.localizeArguments = localizeArguments; + } + + private void throwException(String locKey, String argument) { + if (localizeArguments) { + argument = cm.format(argument, argument); + } + String msg = cm.format(locKey, null, argument); + throw new IllegalArgumentException(msg); + } + + /** + * Asserts the argument is not null. If argument is null, throws localized IllegalArgumentException. + * + * @param type of the object to check + * @param o object to check + * @param argument exception message + * @return original obkect + */ + public T assertNotNull(T o, String argument) { + if (o == null) { + throwException(DBMessages.ASSERT_NOT_NULL, argument); + } + return o; + } + + /** + * Asserts the argument is null. If argument is not null, throws localized IllegalArgumentException. + * + * @param type of the object to check + * @param o object to check + * @param argument exception message + * @return original obkect + */ + public T assertNull(T o, String argument) { + if (o != null) { + throwException(DBMessages.ASSERT_NULL, argument); + } + return o; + } + + /** + * Asserts that argument string is not blank. If argument is null or blank, throws localized + * IllegalArgumentException + * + * @param o object to check + * @param argument exception message + * @return original obkect + */ + public String assertNotBlank(String o, String argument) { + if (o == null || o.length() == 0) { + throwException(DBMessages.ASSERT_NOT_BLANK, argument); + } + return o; + } + + /** + * Asserts that argument string is blank. If argument is not blank, throws localized + * IllegalArgumentException + * + * @param s string to check + * @param argument exception message + * @return original obkect + */ + public String assertBlank(String s, String argument) { + if (s != null && s.length() > 0) { + throwException(DBMessages.ASSERT_BLANK, argument); + } + return s; + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/OperationBuilder.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/OperationBuilder.java new file mode 100644 index 00000000..55e2d407 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/OperationBuilder.java @@ -0,0 +1,29 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +public abstract class OperationBuilder { + + public abstract OperationBuilder addBind(final SQLParam param); +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/PropertiesResolver.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/PropertiesResolver.java new file mode 100644 index 00000000..605fb233 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/PropertiesResolver.java @@ -0,0 +1,160 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.util.*; + +/** + * Resolver of properties in UNIX/ant style. + * Example of usage : + * + * Properties p = new Properties(); + * p.setProperty("p1","value1"); + * p.setProperty("p2","Value of p1 is ${p1}"); + * p = PropertiesResolver.resolveProperties(p); + * + * + * It is shield against recursion. + * + * @author kitko + * + */ +public class PropertiesResolver { + + private PropertiesResolver() { + //empty + } + + /** Resolves properties + * + * @param properties properties we want to resolve + * @param resolvedProperties already known properties + * @return resolved properties + */ + public static Properties resolveProperties(Properties properties, Properties resolvedProperties) { + if (properties == null) { + return null; + } + return resolveProperties(copyProperties(properties), copyProperties(resolvedProperties), new HashSet(5)); + } + + /** + * Resolve properties containing already known values + * + * @param properties properties + * @return resolved properties + */ + public static Properties resolveProperties(Properties properties) { + if (properties == null) { + return null; + } + Properties copy = copyProperties(properties); + return resolveProperties(copy, new Properties(), new HashSet(5)); + } + + private static Properties resolveProperties(Properties properties, Properties resolvedProperties, + Set justResolving) { + for (Map.Entry entry : properties.entrySet()) { + String value = (String) entry.getValue(); + if (value.contains("$")) { + value = resolveName((String) entry.getKey(), properties, resolvedProperties, justResolving); + entry.setValue(value); + } + } + return properties; + } + + private static String resolveName(String name, Properties properties, Properties resolvedProperties, + Set justResolving) { + String value = null; + if (!justResolving.isEmpty()) { + value = resolvedProperties.getProperty(name); + } + if (value != null) { + return value; + } + if (justResolving.contains(name)) { + value = "RECURSION"; + return value; + } + value = properties.getProperty(name); + if (value == null) { + value = "NOT_RESOLVED"; + return value; + } else { + justResolving.add(name); + value = resolveValue(value, properties, resolvedProperties, justResolving); + justResolving.remove(name); + resolvedProperties.setProperty(name, value); + return value; + } + } + + private static String resolveValue(String value, Properties properties, Properties resolvedProperties, + Set justResolving) { + if (value == null) { + return null; + } + int index = 0; + int length = value.length(); + StringBuilder result = new StringBuilder(); + while (index >= 0 && index < length) { + int varStart = value.indexOf("${", index); + if (varStart >= 0) { + int varEnd = value.indexOf('}', varStart); + if (varEnd >= 0) { + String varSubstring = value.substring(varStart, varEnd + 1); + String varName = varSubstring.substring(2, varSubstring.length() - 1); + String varValue = resolveName(varName, properties, resolvedProperties, justResolving); + if ("NOT_RESOLVED".equals(varValue)) { + varValue = varSubstring; + } else if ("RECUSRION".equals(varValue)) { + varValue = "${RECURSION!!!_" + varName + "}"; + } + result.append(value.substring(index, varStart)); + result.append(varValue); + index = varEnd + 1; + } else { + result.append(value.substring(index, value.length())); + index = value.length(); + } + } else { + result.append(value.substring(index, value.length())); + index = value.length(); + } + } + return result.toString(); + } + + private static Properties copyProperties(Properties properties) { + Properties result = new Properties(); + if (properties == null) { + return result; + } + for (Map.Entry entry : properties.entrySet()) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLParam.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLParam.java new file mode 100644 index 00000000..af47fbde --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLParam.java @@ -0,0 +1,247 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.sql.Types; + +/** + * The SQL parameter / util class + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public final class SQLParam { + + private String _name; + + private Object _value; + + private int _sqlType; + + private final String _quotedName; + + /** + * The Sql param is a pair of value and its sqlType + * + * @param name name of the attribute + * @param value value + * @param sqlType sql type + */ + public SQLParam(String name, Object value, int sqlType) { + this(name, value, sqlType, name); + } + + /** + * The Sql param is a pair of value and its sqlType + * + * @param name name of the attribute + * @param value value + * @param sqlType sql type + * @param quotedName quoted name + */ + public SQLParam(String name, Object value, int sqlType, String quotedName) { + if (name == null || name.length() == 0) { + //TODO localize this + throw new IllegalArgumentException("SQL param name should be not null"); + } + _name = name; + _value = value; + _sqlType = sqlType; + _quotedName = quotedName; + } + + /** + * The Sql param is a pair of value and its sqlType + * + * @param name name of the attribute + * @param value value + */ + public SQLParam(String name, Object value) { + this(name, value, Types.NULL, name); + } + + /** + * Accessor for the quoted name property + * + * @return the _name + */ + public String getQuotedName() { + return _quotedName; + } + + /** + * Accessor for the name property + * + * @return the _name + */ + public String getName() { + return _name; + } + + /** + * The param value + * + * @return a value + */ + public Object getValue() { + return _value; + } + + /** + * Sql Type + * + * @return a type + */ + public int getSqlType() { + return _sqlType; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (obj.getClass() != this.getClass())) { + return false; + } + SQLParam other = (SQLParam) obj; + return ((_name == null ? other._name == null : _name.equals(other._name)) + || (_name != null && _name.equals(other._name))) + && (_value == other._value || (_value != null && _value.equals(other._value))) + && _sqlType == other._sqlType; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + (null == _name ? 0 : _name.hashCode()); + hash = 31 * hash + (null == _value ? 0 : _value.hashCode()); + hash = 31 * hash + _sqlType; + return hash; + } + + @Override + public String toString() { + StringBuilder ret = new StringBuilder(); + if (getName() != null) { + ret.append(getName()); + ret.append("="); + } + ret.append("\"").append(getValue()).append("\""); + switch (getSqlType()) { + case Types.ARRAY: + ret.append(":[ARRAY]]"); + break; + case Types.BIGINT: + ret.append(":[BIGINT]"); + break; + case Types.BINARY: + ret.append(":[BINARY]"); + break; + case Types.BIT: + ret.append(":[BIT]"); + break; + case Types.BLOB: + ret.append(":[BLOB]"); + break; + case Types.BOOLEAN: + ret.append(":[BOOLEAN]"); + break; + case Types.CHAR: + ret.append(":[CHAR]"); + break; + case Types.CLOB: + ret.append(":[CLOB]"); + break; + case Types.DATALINK: + ret.append(":[DATALINK]"); + break; + case Types.DATE: + ret.append(":[DATE]"); + break; + case Types.DECIMAL: + ret.append(":[DECIMAL]"); + break; + case Types.DISTINCT: + ret.append(":[DISTINCT]"); + break; + case Types.DOUBLE: + ret.append(":[DOUBLE]"); + break; + case Types.FLOAT: + ret.append(":[FLOAT]"); + break; + case Types.INTEGER: + ret.append(":[INTEGER]"); + break; + case Types.JAVA_OBJECT: + ret.append(":[JAVA_OBJECT]"); + break; + case Types.LONGVARBINARY: + ret.append(":[LONGVARBINARY]"); + break; + case Types.LONGVARCHAR: + ret.append(":[LONGVARCHAR]"); + break; + case Types.NULL: + break; + case Types.NUMERIC: + ret.append(":[NUMERIC]"); + break; + case Types.OTHER: + ret.append(":[OTHER]"); + break; + case Types.REAL: + ret.append(":[REAL]"); + break; + case Types.REF: + ret.append(":[REF]"); + break; + case Types.SMALLINT: + ret.append(":[SMALLINT]"); + break; + case Types.STRUCT: + ret.append(":[STRUCT]"); + break; + case Types.TIME: + ret.append(":[TIME]"); + break; + case Types.TIMESTAMP: + ret.append(":[TIMESTAMP]"); + break; + case Types.TINYINT: + ret.append(":[TINYINT]"); + break; + case Types.VARBINARY: + ret.append(":[VARBINARY]"); + break; + case Types.VARCHAR: + ret.append(":[VARCHAR]"); + break; + default: + ret.append(":[SQL Type:").append(getSqlType()).append("]"); + } + return ret.toString(); + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLUtil.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLUtil.java new file mode 100644 index 00000000..0e5220da --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/SQLUtil.java @@ -0,0 +1,997 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import org.identityconnectors.common.Assertions; +import org.identityconnectors.common.CollectionUtil; +import org.identityconnectors.common.IOUtil; +import org.identityconnectors.common.StringUtil; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.common.logging.Log; +import org.identityconnectors.framework.common.exceptions.ConnectorException; + +/** + * The SQL helper/util class. + */ +public final class SQLUtil { + + private static final Log LOG = Log.getLog(SQLUtil.class); + + /** + * Never allow this to be instantiated. + */ + private SQLUtil() { + throw new AssertionError(); + } + + /** + * Get the connection from the datasource. + * + * @param datasourceName datasource JNDI name + * @param env properties + * @return the connection get from default jndi context + */ + public static Connection getDatasourceConnection(final String datasourceName, final Properties env) { + try { + LOG.ok("Datasource: {0}", datasourceName); + LOG.ok("Properties"); + for (final String propertyName : env.stringPropertyNames()) { + LOG.ok(propertyName + ": {0}", env.getProperty(propertyName)); + } + final javax.naming.InitialContext ic = getInitialContext(env); + LOG.ok("Initial context: {0}", ic); + final DataSource ds = (DataSource) ic.lookup("java:/comp/env/" + datasourceName); + return ds.getConnection(); + } catch (Exception e) { + throw ConnectorException.wrap(e); + } + } + + /** + * Get the connection from the dataSource with specified user and password. + * + * @param datasourceName datasource JNDI name + * @param user DB user + * @param password DB password + * @param env properties + * @return the connection get from dataSource + */ + public static Connection getDatasourceConnection( + final String datasourceName, final String user, GuardedString password, final Properties env) { + try { + LOG.ok("Datasource: {0}", datasourceName); + LOG.ok("User: {0}", user); + for (final String propertyName : env.stringPropertyNames()) { + LOG.ok("Properties"); + LOG.ok(propertyName + ": {0}", env.getProperty(propertyName)); + } + javax.naming.InitialContext ic = getInitialContext(env); + LOG.ok("Initial context created"); + final DataSource ds = (DataSource) ic.lookup("java:/comp/env/" + datasourceName); + LOG.ok("Datasource context created"); + final Connection[] ret = new Connection[1]; + password.access(new GuardedString.Accessor() { + + @Override + public void access(char[] clearChars) { + try { + ret[0] = ds.getConnection(user, new String(clearChars)); + } catch (UnsupportedOperationException | SQLFeatureNotSupportedException nse) { + try { + // some data Sources like HikariCP or Tomcat's BasicDataSource do not support anymore + // getConnection(String username, String password) + ret[0] = ds.getConnection(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } catch (SQLException e) { + // checked exception are not allowed in the access method + // Lets use the exception softening pattern + throw new RuntimeException(e); + } + } + }); + return ret[0]; + } catch (Exception e) { + throw ConnectorException.wrap(e); + } + } + + /** + * Get the connection from the dataSource with specified user and password. + * + * @param datasourceName datasource JNDI name + * @param user DB user + * @param password DB password + * @return the connection get from dataSource + */ + public static Connection getDatasourceConnection( + final String datasourceName, + final String user, + GuardedString password) { + + return getDatasourceConnection(datasourceName, user, password, null); + } + + /** + * Get the initial context method. + * + * @param env environment hastable is null or empty aware + * @return The Context + * @throws NamingException + */ + private static javax.naming.InitialContext getInitialContext(final Properties env) throws NamingException { + return (env == null || env.isEmpty()) + ? new InitialContext() + : new InitialContext(env); + } + + /** + * Get the connection from the datasource. + * + * @param datasourceName datasource JNDI name + * @return the connection get from default jndi context + */ + public static Connection getDatasourceConnection(final String datasourceName) { + return getDatasourceConnection(datasourceName, null); + } + + /** + * Gets a {@link java.sql.Connection} using the basic driver manager. + * + * @param driver jdbc driver name + * @param url jdbc connection url + * @param login jdbc login name + * @param password jdbc password + * @return a valid connection + */ + public static Connection getDriverMangerConnection( + final String driver, final String url, final String login, final GuardedString password) { + // create the connection base on the configuration.. + final Connection[] ret = new Connection[1]; + try { + // load the driver class.. + Class.forName(driver); + // get the database URL.. + // check if there is authentication involved. + if (StringUtil.isNotBlank(login)) { + password.access(new GuardedString.Accessor() { + + @Override + public void access(char[] clearChars) { + try { + ret[0] = DriverManager.getConnection(url, login, new String(clearChars)); + } catch (SQLException e) { + // checked exception are not allowed in the access method + // Lets use the exception softening pattern + throw new RuntimeException(e); + } + } + }); + } else { + ret[0] = DriverManager.getConnection(url); + } + // turn off auto-commit + try { + ret[0].setAutoCommit(false); + } catch (SQLException expected) { + LOG.error(expected, "setAutoCommit() exception"); + } + } catch (Exception e) { + throw ConnectorException.wrap(e); + } + return ret[0]; + } + + /** + * Ignores any exception thrown by the {@link Connection} parameter when closed, it also checks for {@code null}. + * + * @param conn JDBC connection to rollback. + */ + public static void rollbackQuietly(final Connection conn) { + try { + if (conn != null && !conn.isClosed()) { + conn.rollback(); + } + } catch (SQLException expected) { + //expected + } + } + + /** + * Ignores any exception thrown by the {@link DatabaseConnection} parameter when closed, it also checks for + * {@code null}. + * + * @param conn DatabaseConnection to rollback. + */ + public static void rollbackQuietly(final DatabaseConnection conn) { + if (conn != null) { + rollbackQuietly(conn.getConnection()); + } + } + + /** + * Ignores any exception thrown by the {@link Connection} parameter when closed, it also checks for {@code null}. + * + * @param conn JDBC connection to close. + */ + public static void closeQuietly(final Connection conn) { + try { + if (conn != null && !conn.isClosed()) { + conn.close(); + } + } catch (SQLException expected) { + //expected + } + } + + /** + * Ignores any exception thrown by the {@link Connection} parameter when closed, it also checks for {@code null}. + * + * @param conn DatabaseConnection to close. + */ + public static void closeQuietly(final DatabaseConnection conn) { + if (conn != null) { + closeQuietly(conn.getConnection()); + } + } + + /** + * Ignores any exception thrown by the {@link Statement#close()} method. + * + * @param stmt {@link Statement} to close. + */ + public static void closeQuietly(final Statement stmt) { + try { + if (stmt != null) { + stmt.close(); + } + } catch (SQLException expected) { + //expected + } + } + + /** + * Closes the {@link ResultSet} and ignores any {@link Exception} that may be thrown by the + * {@link ResultSet#close()} method. + * + * @param rset {@link ResultSet} to close quitely. + */ + public static void closeQuietly(final ResultSet rset) { + try { + if (rset != null) { + rset.close(); + } + } catch (SQLException expected) { + //expected + } + } + + /** + * Date to string. + * + * @param value Date value + * @return String value + */ + public static String date2String(final Date value) { + return value.toString(); + } + + /** + * Time to String format. + * + * @param value Time value + * @return String value + */ + public static String time2String(final Time value) { + return value.toString(); + } + + /** + * Convert timestamp to string. + * + * @param value Timestamp + * @return the string value + */ + public static String timestamp2String(final Timestamp value) { + return value.toString(); + } + + /** + * String to Time. + * + * @param param String + * @return the Time value + */ + public static Time string2Time(final String param) { + final DateFormat dfmt = DateFormat.getTimeInstance(); + Time parsedTime; + try { + parsedTime = Time.valueOf(param); + } catch (IllegalArgumentException e) { + // Locale parsed time, possible lost of precision + try { + parsedTime = new Time(dfmt.parse(param).getTime()); + } catch (ParseException pe) { + throw new IllegalArgumentException(pe); + } + } + return new Time(parsedTime.getTime()); + } + + /** + * String to Date. + * + * @param param the String value + * @return Date value + */ + public static Date string2Date(final String param) { + final DateFormat dfmt = DateFormat.getDateInstance(); + java.sql.Date parsedDate; + try { + parsedDate = Date.valueOf(param); + } catch (IllegalArgumentException e) { + // Wrong string, cloud be a string number + try { + parsedDate = new Date(Long.valueOf(param)); + } catch (NumberFormatException expected) { + // Locale parsed date, possible lost of precision + try { + parsedDate = new Date(dfmt.parse(param).getTime()); + } catch (ParseException pe) { + throw new IllegalArgumentException(pe); + } + } + } + return parsedDate; + } + + /** + * Convert string to Timestamp + * + * @param param String value + * @return Timestamp value + */ + public static Timestamp string2Timestamp(final String param) { + final DateFormat dfmt = DateFormat.getDateTimeInstance(); + Timestamp parsedTms; + try { + parsedTms = Timestamp.valueOf(param); + } catch (IllegalArgumentException e) { + // Wrong string, cloud be a number + try { + parsedTms = new Timestamp(Long.valueOf(param)); + } catch (NumberFormatException expected) { + // Locale parsed date, possible lost of precision + try { + parsedTms = new Timestamp(dfmt.parse(param).getTime()); + } catch (ParseException pe) { + throw new IllegalArgumentException(pe); + } + } + } + return parsedTms; + } + + /** + * Convert String to boolean. + * + * @param val string value + * @return Boolean value + */ + public static Boolean string2Boolean(final String val) { + if (val == null) { + return Boolean.FALSE; + } + return Boolean.valueOf(val); + } + + /** + * The null param vlaue normalizator. + * + * @param sql SQL query + * @param params list + * @param out out param list + * @return the modified string + */ + public static String normalizeNullValues(final String sql, final List params, final List out) { + StringBuilder ret = new StringBuilder(); + int size = (params == null) ? 0 : params.size(); + //extend for extra space + final String sqlext = sql + " "; + String[] values = sqlext.split("\\?"); + if (values.length != (size + 1)) { + throw new IllegalStateException("bind.params.count.not.same"); + } + for (int i = 0; i < values.length; i++) { + String string = values[i]; + ret.append(string); + if (params != null && i < params.size()) { + final SQLParam param = params.get(i); + if (param == null || (param.getValue() == null && param.getSqlType() == Types.NULL)) { + ret.append("null"); + } else { + ret.append("?"); + out.add(param); + } + } + } + //return sql less the extra space + return ret.substring(0, ret.length() - 1); + } + + /** + * Make a blob conversion. + * + * @param blobValue blob + * @return a converted value + * @throws SQLException if anything goes wrong + */ + public static byte[] blob2ByteArray(final Blob blobValue) throws SQLException { + byte[] newValue = null; + // convert from Blob to byte[] + InputStream is = blobValue.getBinaryStream(); + try { + newValue = IOUtil.readInputStreamBytes(is, true); + } catch (IOException e) { + throw ConnectorException.wrap(e); + } finally { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + return newValue; + } + + /** + * Binds the "?" markers in SQL statement with the parameters given as values. It concentrates the + * replacement of all params.GuardedString are handled so the password is never visible. + * + * @param statement SQL statement + * @param params a List of the object arguments + * @throws SQLException an exception in statement + */ + public static void setParams(final PreparedStatement statement, final List params) throws SQLException { + if (statement == null || params == null) { + return; + } + for (int i = 0; i < params.size(); i++) { + final int idx = i + 1; + final SQLParam parm = params.get(i); + final int sqlType = parm.getSqlType(); + final SQLParam val = new SQLParam(parm.getName(), attribute2jdbcValue(parm.getValue(), sqlType), sqlType); + setParam(statement, idx, val); + } + } + + /** + * Binds the "?" markers in SQL statement with the parameters given as values. It concentrates the + * replacement of all params. GuardedString are handled so the password is never visible. + * + * @param statement SQL statement + * @param params a List of the object arguments + * @throws SQLException an exception in statement + */ + public static void setParams(final CallableStatement statement, final List params) throws SQLException { + //The same as for prepared statements + setParams((PreparedStatement) statement, params); + } + + /** + * Set the statement parameter. It is ready for overloading if necessary. + * + * @param stmt a PreparedStatement to set the params + * @param idx an index of the parameter + * @param parm a parameter Value + * @throws SQLException a SQL exception + */ + static void setParam(final PreparedStatement stmt, final int idx, final SQLParam parm) throws SQLException { + // Guarded string conversion + if (parm.getValue() instanceof GuardedString) { + setGuardedStringParam(stmt, idx, (GuardedString) parm.getValue()); + } else { + setSQLParam(stmt, idx, parm); + } + } + + /** + * Read one row from database result set and convert a columns to attribute set. + * + * @param resultSet database data + * @return The transformed attribute set + * @throws SQLException if anything goes wrong + */ + public static Map getColumnValues(final ResultSet resultSet) throws SQLException { + Assertions.nullCheck(resultSet, "resultSet"); + Map ret = CollectionUtil.newCaseInsensitiveMap(); + final ResultSetMetaData meta = resultSet.getMetaData(); + int count = meta.getColumnCount(); + for (int i = 1; i <= count; i++) { + final String name = meta.getColumnName(i); + final int sqlType = meta.getColumnType(i); + final SQLParam param = getSQLParam(resultSet, i, name, sqlType); + ret.put(name, param); + } + return ret; + } + + /** + * Retrieve the SQL value from result set. + * + * @param resultSet the result set + * @param i index + * @param name param name + * @param sqlType expected SQL type or Types.NULL for generic + * @return the object return the retrieved object + * @throws SQLException any SQL error + */ + public static SQLParam getSQLParam( + final ResultSet resultSet, final int i, final String name, final int sqlType) throws SQLException { + Assertions.nullCheck(resultSet, "resultSet"); + Object object; + switch (sqlType) { + //Known conversions + case Types.NULL: + object = resultSet.getObject(i); + break; + case Types.DECIMAL: + case Types.NUMERIC: + object = resultSet.getBigDecimal(i); + break; + case Types.DOUBLE: + case Types.FLOAT: + case Types.REAL: + case Types.INTEGER: + case Types.BIGINT: +// object = resultSet.getDouble(i); double does not support update to null +// object = resultSet.getFloat(i); float does not support update to null +// object = resultSet.getInt(i); int does not support update to null + object = resultSet.getObject(i); + break; + case Types.TINYINT: + object = resultSet.getByte(i); + break; + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + object = resultSet.getObject(i); + break; + case Types.TIMESTAMP: + object = resultSet.getTimestamp(i); + break; + case Types.DATE: + object = resultSet.getDate(i); + break; + case Types.TIME: + object = resultSet.getTime(i); + break; + case Types.BIT: + case Types.BOOLEAN: + object = resultSet.getBoolean(i); + break; + default: + object = resultSet.getString(i); + } + return new SQLParam(name, object, sqlType); + } + + /** + * Convert database type to connector supported set of attribute types Can be redefined for different databases. + * + * @param sqlType #{@link Types} + * @return a connector supported class + */ + public static Class getSQLAttributeType(final int sqlType) { + Class ret; + switch (sqlType) { + //Known conversions + case Types.DECIMAL: + case Types.NUMERIC: + ret = BigDecimal.class; + break; + case Types.DOUBLE: + ret = Double.class; + break; + case Types.FLOAT: + case Types.REAL: + ret = Float.class; + break; + case Types.INTEGER: + ret = Integer.class; + break; + case Types.BIGINT: + ret = Long.class; + break; + case Types.TINYINT: + ret = Byte.class; + break; + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + ret = byte[].class; + break; + case Types.BIT: + case Types.BOOLEAN: + ret = Boolean.class; + break; + default: + ret = String.class; + } + return ret; + } + + /** + * Set a parameter to statement. The conversion to required database type is expected to be done. + * + * @param stmt the statement to set + * @param idx index of the parameter + * @param parm the SQLParam value + * @throws SQLException something wrong + */ + public static void setSQLParam( + final PreparedStatement stmt, final int idx, final SQLParam parm) throws SQLException { + Assertions.nullCheck(stmt, "statement"); + Assertions.nullCheck(parm, "parm"); + // Handle the null value + final int sqlType = parm.getSqlType(); + final Object val = parm.getValue(); + //Set the null value + if (val == null) { + stmt.setNull(idx, sqlType); + return; + } + //Set the generics + if (sqlType == Types.NULL) { + stmt.setObject(idx, val); + return; + } + //Set specific object + if (val instanceof BigDecimal) { + stmt.setBigDecimal(idx, (BigDecimal) val); + } else if (val instanceof Double) { + stmt.setDouble(idx, (Double) val); + } else if (val instanceof Float) { + stmt.setFloat(idx, (Float) val); + } else if (val instanceof Integer) { + stmt.setInt(idx, (Integer) val); + } else if (val instanceof Long) { + stmt.setLong(idx, (Long) val); + } else if (val instanceof BigInteger) { + stmt.setLong(idx, ((BigInteger) val).longValue()); + } else if (val instanceof Byte) { + stmt.setByte(idx, (Byte) val); + } else if (val instanceof Integer) { + stmt.setInt(idx, (Integer) val); + } else if (val instanceof InputStream) { + stmt.setBinaryStream(idx, (InputStream) val, 10000); + } else if (val instanceof Blob) { + stmt.setBlob(idx, (Blob) val); + } else if (val instanceof byte[]) { + stmt.setBytes(idx, (byte[]) val); + } else if (val instanceof Timestamp) { + stmt.setTimestamp(idx, (Timestamp) val); + } else if (val instanceof java.sql.Date) { + stmt.setDate(idx, (java.sql.Date) val); + } else if (val instanceof java.sql.Time) { + stmt.setTime(idx, (java.sql.Time) val); + } else if (val instanceof Boolean) { + stmt.setBoolean(idx, (Boolean) val); + } else if (val instanceof String) { + stmt.setString(idx, (String) val); + } else { + stmt.setObject(idx, val); + } + } + + /** + * The conversion to required attribute type. + * + * @param value to be converted to an attribute + * @throws SQLException something is not ok + * @return a attribute's supported object + */ + public static Object jdbc2AttributeValue(final Object value) throws SQLException { + Object ret = null; + if (value == null) { + return ret; + } + if (value instanceof Blob) { + ret = blob2ByteArray((Blob) value); + } else if (value instanceof java.sql.Timestamp) { + ret = timestamp2String((java.sql.Timestamp) value); + } else if (value instanceof java.sql.Time) { + ret = time2String((java.sql.Time) value); + } else if (value instanceof java.sql.Date) { + ret = date2String((java.sql.Date) value); + } else if (value instanceof java.util.Date) { + //convert date to String + ret = ((java.util.Date) value).toString(); + /* } else if (value instanceof Long) { + * ret = value; + * } else if (value instanceof Character) { + * ret = value; + * } else if (value instanceof Double) { + * ret = value; + * } else if (value instanceof Float) { + * ret = value; + * } else if (value instanceof Integer) { + * ret = value; + * } else if (value instanceof Boolean) { + * ret = value; + * } else if (value instanceof Byte[]) { + * ret = value; + * } else if (value instanceof BigDecimal) { + * ret = value; + * } else if (value instanceof BigInteger) { + * ret = value; */ + } else { + // converted to string leads to error in contract tests + // TODO figure out, which type fail. It could be Character[] + ret = value; + } + return ret; + } + + /** + * Convert the attribute to expected jdbc type using java conversions Some database strategy sets all attributes as + * string, other convert them first and than set as native. + * + * @param value the value to be converted + * @param sqlType the target sql type + * @return the converted object value + * @throws SQLException any SQL error + */ + public static Object attribute2jdbcValue(final Object value, int sqlType) throws SQLException { + if (value == null) { + return null; + } + switch (sqlType) { + //Known conversions + case Types.DECIMAL: + case Types.NUMERIC: + case Types.DOUBLE: + if (value instanceof BigDecimal) { + return value; + } else if (value instanceof Double) { + return value; + } else if (value instanceof Float) { + return value; + } else if (value instanceof String) { + return Double.valueOf((String) value); + } else { + return Double.valueOf(value.toString()); + } + case Types.FLOAT: + case Types.REAL: + if (value instanceof BigDecimal) { + return value; + } else if (value instanceof Float) { + return value; + } else if (value instanceof Double) { + return value; + } else if (value instanceof String) { + return Float.valueOf((String) value); + } else { + return Float.valueOf(value.toString()); + } + case Types.INTEGER: + case Types.BIGINT: + if (value instanceof BigInteger) { + return value; + } else if (value instanceof Long) { + return value; + } else if (value instanceof Integer) { + return value; + } else if (value instanceof String) { + return Long.valueOf((String) value); + } else { + return Long.valueOf(value.toString()); + } + case Types.TIMESTAMP: + if (value instanceof String) { + return string2Timestamp((String) value); + } + break; + case Types.DATE: + if (value instanceof String) { + return string2Date((String) value); + } + break; + case Types.TIME: + if (value instanceof String) { + return string2Time((String) value); + } + break; + case Types.BIT: + case Types.BOOLEAN: + if (value instanceof String) { + return string2Boolean((String) value); + } + break; + case Types.LONGVARCHAR: + case Types.VARCHAR: + case Types.CHAR: + if (value instanceof String) { + return value; + } + return value.toString(); + } + return value; + } + + /** + * The helper guardedString bind method. + * + * @param stmt to bind to + * @param idx index of the object + * @param guard a GuardedString parameter + * @throws SQLException any SQL error + */ + public static void setGuardedStringParam(final PreparedStatement stmt, final int idx, final GuardedString guard) + throws SQLException { + try { + guard.access(new GuardedString.Accessor() { + + @Override + public void access(char[] clearChars) { + try { + //Never use setString, the DB2 database will fail for secured columns + stmt.setObject(idx, new String(clearChars)); + } catch (SQLException e) { + // checked exception are not allowed in the access method + // Lets use the exception softening pattern + throw new RuntimeException(e); + } + } + }); + } catch (RuntimeException e) { + // determine if there's a SQLException and re-throw that.. + if (e.getCause() instanceof SQLException) { + throw (SQLException) e.getCause(); + } + throw e; + } + } + + /** + * Selects single value (first column) from select. It fetches only first row, does not check whether more rows are + * returned by select. If no row is returned, returns null + * + * @param conn JDBC connection + * @param sql Select statement with or without parameters + * @param params Parameters to use in statement + * @return first row and first column value + * @throws SQLException any SQL error + */ + public static Object selectSingleValue(final Connection conn, final String sql, final SQLParam... params) + throws SQLException { + PreparedStatement st = null; + ResultSet rs = null; + try { + st = conn.prepareStatement(sql); + setParams(st, Arrays.asList(params)); + rs = st.executeQuery(); + Object value; + if (rs.next()) { + //If needed , switch to getSQLParam + value = rs.getObject(1); + return value; + } + return null; + } finally { + closeQuietly(rs); + closeQuietly(st); + } + } + + /** + * Selects all rows from select. It uses {@link ResultSet#getMetaData()} to find columns count and use + * {@link ResultSet#getObject(int)} to retrieve column value. + * + * @param conn JDBC connection + * @param sql SQL select with or without params + * @param params SQL parameters + * @return list of selected rows + * @throws SQLException any SQL error + */ + public static List selectRows(final Connection conn, final String sql, final SQLParam... params) + throws SQLException { + PreparedStatement st = null; + ResultSet rs = null; + List rows = new ArrayList(); + try { + st = conn.prepareStatement(sql); + setParams(st, Arrays.asList(params)); + rs = st.executeQuery(); + final ResultSetMetaData metaData = rs.getMetaData(); + while (rs.next()) { + Object[] row = new Object[metaData.getColumnCount()]; + for (int i = 0; i < row.length; i++) { + final SQLParam param = getSQLParam(rs, i + 1, metaData.getColumnName(i + 1), metaData.getColumnType( + i + 1)); + row[i] = jdbc2AttributeValue(param.getValue()); + } + rows.add(row); + } + return rows; + } finally { + closeQuietly(rs); + closeQuietly(st); + } + } + + /** + * Executes DML sql statement. This can be useful to execute insert/update/delete or some database specific + * statement in one call + * + * @param conn connection + * @param sql SQL query + * @param params SQL parameters + * @return number of rows affected as defined by {@link PreparedStatement#executeUpdate()} + * @throws SQLException any SQL error + */ + public static int executeUpdateStatement(final Connection conn, final String sql, final SQLParam... params) + throws SQLException { + PreparedStatement st = null; + try { + st = conn.prepareStatement(sql); + setParams(st, Arrays.asList(params)); + return st.executeUpdate(); + } finally { + closeQuietly(st); + } + } +} diff --git a/java/commons/db/src/main/java/net/tirasa/connid/commons/db/UpdateSetBuilder.java b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/UpdateSetBuilder.java new file mode 100644 index 00000000..611b6288 --- /dev/null +++ b/java/commons/db/src/main/java/net/tirasa/connid/commons/db/UpdateSetBuilder.java @@ -0,0 +1,95 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import java.util.ArrayList; +import java.util.List; +import org.identityconnectors.common.CollectionUtil; + +/** + * The update set builder create the database update statement. + *

+ * The main functionality is create set part of update statement from Attribute set

+ * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class UpdateSetBuilder extends OperationBuilder { + + private final List params = new ArrayList(); + + private final StringBuilder set = new StringBuilder(); + + /** + * Add column name and value pair. + * + * @param param value + * @return self + */ + @Override + public UpdateSetBuilder addBind(final SQLParam param) { + return addBind(param, "?"); + } + + /** + * Add column name and expression value pair. + * + * @param param the value to bind + * @param expression the expression + * @return self + */ + public UpdateSetBuilder addBind(SQLParam param, String expression) { + if (set.length() > 0) { + set.append(" , "); + } + set.append(param.getName()).append(" = ").append(expression); + params.add(param); + return this; + } + + /** + * Build the set SQL. + * + * @return The update set clause + */ + public String getSQL() { + return set.toString(); + } + + /** + * Add the update value. + * + * @param param SQL parameter + */ + public void addValue(SQLParam param) { + params.add(param); + } + + /** + * @return the param values + */ + public List getParams() { + return CollectionUtil.newReadOnlyList(params); + } +} diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages.properties new file mode 100644 index 00000000..cbf5fd13 --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=Argument [{0}] cannot be null +assert.null=Argument [{0}] must be null +assert.notBlank=Argument [{0}] cannot be blank +assert.blank=Argument [{0}] must be blank diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_de.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_de.properties new file mode 100644 index 00000000..573a424f --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_de.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=Das Argument [{0}] darf nicht null sein +assert.null=Das Argument [{0}] muss null sein +assert.notBlank=Das Argument [{0}] darf nicht leer sein +assert.blank=Das Argument [{0}] muss leer sein diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_es.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_es.properties new file mode 100644 index 00000000..a373c1e1 --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_es.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=El argumento [{0}] no puede ser nulo +assert.null=El argumento [{0}] debe ser nulo +assert.notBlank=El argumento [{0}] no puede estar en blanco +assert.blank=El argumento [{0}] debe estar en blanco diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_fr.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_fr.properties new file mode 100644 index 00000000..7e860557 --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_fr.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=L\u2019argument [{0}] ne peut pas \u00eatre null. +assert.null=L\u2019argument [{0}] doit \u00eatre null. +assert.notBlank=L\u2019argument [{0}] ne peut pas \u00eatre vide. +assert.blank=L\u2019argument [{0}] doit \u00eatre vide. diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_it.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_it.properties new file mode 100644 index 00000000..d93a5677 --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_it.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=L\u2019argomento [{0}] non pu\u00f2 essere nullo +assert.null=L\u2019argomento [{0}] deve essere nullo +assert.notBlank=L\u2019argomento [{0}] non pu\u00f2 essere vuoto +assert.blank=L\u2019argomento [{0}] deve essere vuoto diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ja.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ja.properties new file mode 100644 index 00000000..f4d830ed --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ja.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=\u5f15\u6570 [{0}] \u306f NULL \u306b\u3067\u304d\u307e\u305b\u3093 +assert.null=\u5f15\u6570 [{0}] \u306f NULL \u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059 +assert.notBlank=\u5f15\u6570 [{0}] \u306f\u7a7a\u306b\u3067\u304d\u307e\u305b\u3093 +assert.blank=\u5f15\u6570 [{0}] \u306f\u7a7a\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059 diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ko.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ko.properties new file mode 100644 index 00000000..5a613f01 --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_ko.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=\uc778\uc218 [{0}]\uc740(\ub294) Null\uc77c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +assert.null=\uc778\uc218 [{0}]\uc740(\ub294) Null\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. +assert.notBlank=\uc778\uc218 [{0}]\uc740(\ub294) \ube44\uc6cc \ub458 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. +assert.blank=\uc778\uc218 [{0}]\uc740(\ub294) \ube44\uc6cc \ub450\uc5b4\uc57c \ud569\ub2c8\ub2e4. diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_pt.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_pt.properties new file mode 100644 index 00000000..bae32d9a --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_pt.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=O argumento [{0}] n\u00e3o pode ser nulo +assert.null=O argumento [{0}] deve ser nulo +assert.notBlank=O argumento [{0}] n\u00e3o pode ficar em branco +assert.blank=O argumento [{0}] deve ficar em branco diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh.properties new file mode 100644 index 00000000..69e94891 --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=\u53c2\u6570 [{0}] \u4e0d\u80fd\u4e3a null +assert.null=\u53c2\u6570 [{0}] \u5fc5\u987b\u4e3a null +assert.notBlank=\u53c2\u6570 [{0}] \u4e0d\u80fd\u4e3a\u7a7a +assert.blank=\u53c2\u6570 [{0}] \u5fc5\u987b\u4e3a\u7a7a diff --git a/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh_TW.properties b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh_TW.properties new file mode 100644 index 00000000..3b1d12ac --- /dev/null +++ b/java/commons/db/src/main/resources/net/tirasa/connid/bundles/db/common/Messages_zh_TW.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# Portions Copyrighted 2011 ConnId. +# + +assert.notNull=\u5f15\u6578 [{0}] \u4e0d\u53ef\u70ba\u7a7a +assert.null=\u5f15\u6578 [{0}] \u5fc5\u9808\u70ba\u7a7a +assert.notBlank=\u5f15\u6578 [{0}] \u4e0d\u53ef\u70ba\u7a7a\u767d +assert.blank=\u5f15\u6578 [{0}] \u5fc5\u9808\u70ba\u7a7a\u767d diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseConnectionTests.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseConnectionTests.java new file mode 100644 index 00000000..69afc890 --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseConnectionTests.java @@ -0,0 +1,235 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * DatabaseConnection test class + * + * @version $Revision 1.0$ + * + * @since 1.0 + * + */ +public class DatabaseConnectionTests { + + private static final String LOGIN = "login"; + + private static final String NAME = "name"; + + private static final String TEST_SQL_STATEMENT = "SELECT * FROM dummy"; + + private static final String SELECT_SQL_STATEMENT = "SELECT * FROM dummy WHERE login = ? and name = ?"; + + private List values; + + /** + * + * @throws java.lang.Exception + * + */ + @BeforeEach + public void setUp() throws Exception { + values = new ArrayList(); + values.add(new SQLParam(LOGIN, LOGIN)); + values.add(new SQLParam(NAME, NAME)); + } + + @AfterEach + public void tearDown() throws Exception { + // not used yet + } + + @Test + public void testDatabaseConnection() { + ExpectProxy tp = new ExpectProxy<>(); + DatabaseConnection dbc = new DatabaseConnection(tp.getProxy(Connection.class)); + assertNotNull(dbc); + assertNotNull(dbc.getConnection()); + } + + /** + * + * Test method for {@link DatabaseConnection#dispose()}. + * + */ + @Test + public void testDispose() { + ExpectProxy tp = new ExpectProxy<>(); + tp.expectAndReturn("isClosed", Boolean.FALSE); + tp.expect("close"); + Connection xc = tp.getProxy(Connection.class); + DatabaseConnection dbc = new DatabaseConnection(xc); + dbc.dispose(); + assertTrue(tp.isDone()); + tp = new ExpectProxy<>(); + xc = tp.getProxy(Connection.class); + tp.expectAndReturn("isClosed", Boolean.TRUE); + dbc = new DatabaseConnection(xc); + dbc.dispose(); + assertTrue(tp.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#test()}. + * + */ + @Test + public void testTest() { + ExpectProxy tp = new ExpectProxy<>(); + tp.expectAndReturn("getAutoCommit", Boolean.FALSE); + tp.expect("setAutoCommit"); + tp.expectAndReturn("getAutoCommit", Boolean.TRUE); + tp.expect("setAutoCommit"); + tp.expect("commit"); + DatabaseConnection dbc = new DatabaseConnection(tp.getProxy(Connection.class)); + dbc.test(); + assertTrue(tp.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#getConnection()}. + * + */ + @Test + public void testGetSetConnection() { + ExpectProxy tp = new ExpectProxy<>(); + final Connection xc = tp.getProxy(Connection.class); + DatabaseConnection dbc = new DatabaseConnection(xc); + dbc.getConnection(); + assertTrue(tp.isDone()); + assertNotNull(dbc.getConnection()); + assertSame(xc, dbc.getConnection()); + assertTrue(tp.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#prepareStatement(java.lang.String, java.util.List)}. + * + * @throws Exception * + */ + @Test + public void testPrepareStatementNullValues() throws Exception { + final ExpectProxy tpc = new ExpectProxy<>(); + final ExpectProxy tps = new ExpectProxy<>(); + final PreparedStatement xps = tps.getProxy(PreparedStatement.class); + tpc.expectAndReturn("prepareStatement", xps); + DatabaseConnection dbc = new DatabaseConnection(tpc.getProxy(Connection.class)); + dbc.prepareStatement(TEST_SQL_STATEMENT, null); + assertTrue(tpc.isDone()); + assertTrue(tps.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#prepareStatement(java.lang.String, java.util.List)}. + * + * @throws Exception * + */ + @Test + public void testPrepareStatementEmptyValues() throws Exception { + final ExpectProxy tpc = new ExpectProxy<>(); + final ExpectProxy tps = new ExpectProxy<>(); + final PreparedStatement xps = tps.getProxy(PreparedStatement.class); + tpc.expectAndReturn("prepareStatement", xps); + DatabaseConnection dbc = new DatabaseConnection(tpc.getProxy(Connection.class)); + dbc.prepareStatement(TEST_SQL_STATEMENT, new ArrayList<>()); + assertTrue(tpc.isDone()); + assertTrue(tps.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#prepareStatement(java.lang.String, java.util.List)}. + * + * @throws Exception * + */ + @Test + public void testPrepareStatement() throws Exception { + final ExpectProxy tpc = new ExpectProxy<>(); + final ExpectProxy tps = new ExpectProxy<>(); + final PreparedStatement xps = tps.getProxy(PreparedStatement.class); + tpc.expectAndReturn("prepareStatement", xps); + tps.expectAndReturn("setObject", LOGIN); + tps.expectAndReturn("setObject", NAME); + tps.expectAndReturn("execute", true); + DatabaseConnection dbc = new DatabaseConnection(tpc.getProxy(Connection.class)); + final PreparedStatement ps = dbc.prepareStatement(SELECT_SQL_STATEMENT, values); + ps.execute(); + assertTrue(tpc.isDone()); + assertTrue(tps.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#prepareStatement(java.lang.String, java.util.List)}. + * + * @throws Exception * + */ + @Test + public void testPrepareCall() throws Exception { + final ExpectProxy tpc = new ExpectProxy<>(); + final ExpectProxy tps = new ExpectProxy<>(); + final CallableStatement cs = tps.getProxy(CallableStatement.class); + tpc.expectAndReturn("prepareStatement", cs); + tps.expectAndReturn("setObject", LOGIN); + tps.expectAndReturn("setObject", NAME); + tps.expectAndReturn("execute", true); + DatabaseConnection dbc = new DatabaseConnection(tpc.getProxy(Connection.class)); + final PreparedStatement ps = dbc.prepareStatement(SELECT_SQL_STATEMENT, values); + ps.execute(); + assertTrue(tpc.isDone()); + assertTrue(tps.isDone()); + } + + /** + * + * Test method for {@link DatabaseConnection#commit(org.identityconnectors.common.logging.Log)}. + * + */ + @Test + public void testCommit() { + ExpectProxy tp = new ExpectProxy<>(); + tp.expect("commit"); + DatabaseConnection dbc = new DatabaseConnection(tp.getProxy(Connection.class)); + dbc.commit(); + assertTrue(tp.isDone()); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseFilterTranslatorTests.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseFilterTranslatorTests.java new file mode 100644 index 00000000..250b64c8 --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseFilterTranslatorTests.java @@ -0,0 +1,250 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.identityconnectors.framework.common.objects.AttributeBuilder.build; +import static org.identityconnectors.framework.common.objects.filter.FilterBuilder.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.common.objects.filter.Filter; +import org.identityconnectors.framework.common.objects.filter.FilterBuilder; +import org.junit.jupiter.api.Test; + +/** + * Attempts to test the Connector with the framework. + */ +public class DatabaseFilterTranslatorTests { + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testUnaryFilters() throws Exception { + Attribute attr = build("count", 2); + Filter filters[] = new Filter[] { + equalTo(attr), greaterThan(attr), greaterThanOrEqualTo(attr), lessThan(attr), lessThanOrEqualTo(attr) }; + String ops[] = new String[] { "=", ">", ">=", "<", "<=" }; + List expected = new ArrayList<>(); + expected.add(new SQLParam("count", 2, Types.INTEGER)); + for (int i = 0; i < filters.length; i++) { + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(filters[i]); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals("count " + ops[i] + " ?", b.getWhereClause()); + assertEquals(expected.size(), b.getParams().size()); + } + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testCompositeFilters() throws Exception { + Filter lf = greaterThan(build("count", 4)); + Filter rf = lessThan(build("count", 20)); + List expected = new ArrayList(); + expected.add(new SQLParam("count", 4, Types.INTEGER)); + expected.add(new SQLParam("count", 20, Types.INTEGER)); + // test and + Filter f = FilterBuilder.and(lf, rf); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(f); + assertEquals(1, blist.size()); + FilterWhereBuilder b = blist.get(0); + assertEquals("count > ? AND count < ?", b.getWhereClause()); + // test or + assertEquals(expected.size(), b.getParams().size()); + f = FilterBuilder.or(lf, rf); + DatabaseFilterTranslator tr2 = getDatabaseFilterTranslator(); + blist = tr2.translate(f); + assertEquals(1, blist.size()); + b = blist.get(0); + assertEquals("count > ? OR count < ?", b.getWhereClause()); + assertEquals(expected.size(), b.getParams().size()); + // test xor + // assertEquals(expected, actual); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testCompositeFilterChainNotOr() throws Exception { + Filter lf = greaterThan(build("count", 4)); + Filter rf = lessThan(build("count", 20)); + List expected = new ArrayList<>(); + expected.add(new SQLParam("count", 4, Types.INTEGER)); + expected.add(new SQLParam("count", 20, Types.INTEGER)); + // test and + Filter f = FilterBuilder.or(lf, rf); + Filter not = FilterBuilder.not(f); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(not); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals("count <= ? AND count >= ?", b.getWhereClause()); + assertEquals(expected.size(), b.getParams().size()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testCompositeFilterChainOrAnd() throws Exception { + Filter f1 = greaterThan(build("count", 4)); + Filter f2 = lessThan(build("count", 20)); + Filter f3 = equalTo(build("count", 10)); + List expected = new ArrayList<>(); + expected.add(new SQLParam("count", 4, Types.INTEGER)); + expected.add(new SQLParam("count", 20, Types.INTEGER)); + expected.add(new SQLParam("count", 10, Types.INTEGER)); + // test and + Filter f12 = FilterBuilder.or(f1, f2); + Filter f = FilterBuilder.and(f12, f3); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(f); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals("( count > ? OR count < ? ) AND count = ?", b.getWhereClause()); + assertEquals(expected.size(), b.getParams().size()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testCompositeFilterChainAndOrAndOrAnd() throws Exception { + Filter f1 = equalTo(build("a", 1)); + Filter f2 = equalTo(build("b", 1)); + Filter f3 = equalTo(build("c", 1)); + Filter f4 = equalTo(build("d", 1)); + Filter f5 = equalTo(build("e", 1)); + Filter f6 = equalTo(build("f", 1)); + List expected = new ArrayList<>(); + expected.add(new SQLParam("a", 1)); + expected.add(new SQLParam("b", 1)); + expected.add(new SQLParam("c", 1)); + expected.add(new SQLParam("d", 1)); + expected.add(new SQLParam("e", 1)); + expected.add(new SQLParam("f", 1)); + // test and + Filter f12 = FilterBuilder.or(f1, f2); + Filter f34 = FilterBuilder.and(f3, f4); + Filter f56 = FilterBuilder.or(f5, f6); + Filter f1234 = FilterBuilder.and(f12, f34); + Filter f = FilterBuilder.or(f1234, f56); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(f); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals( + "( ( a = ? OR b = ? ) AND ( c = ? AND d = ? ) ) OR ( e = ? OR f = ? )", b + .getWhereClause()); + assertEquals(expected, b.getParams()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testCompositeFilterChainOrAndNot() throws Exception { + Filter f1 = greaterThan(build("count", 4)); + Filter f2 = lessThan(build("count", 20)); + Filter f3 = equalTo(build("count", 10)); + List expected = new ArrayList<>(); + expected.add(new SQLParam("count", 4, Types.INTEGER)); + expected.add(new SQLParam("count", 20, Types.INTEGER)); + expected.add(new SQLParam("count", 10, Types.INTEGER)); + // test and + Filter f1o2 = FilterBuilder.or(f1, f2); + Filter fn3 = FilterBuilder.not(f3); + Filter f = FilterBuilder.and(f1o2, fn3); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(f); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals("( count > ? OR count < ? ) AND NOT count = ?", b.getWhereClause()); + assertEquals(expected.size(), b.getParams().size()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.DatabaseFilterTranslator}. + * + * @throws Exception + */ + @Test + public void testNotfilter() throws Exception { + Filter gt = greaterThan(build("count", 4)); + Filter f = FilterBuilder.not(gt); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(f); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals("count <= ?", b.getWhereClause()); + List expected = new ArrayList<>(); + expected.add(new SQLParam("count", 4, Types.INTEGER)); + assertEquals(expected.size(), b.getParams().size()); + } + + @Test + public void equalsIgnoreCase() { + Filter f = FilterBuilder.equalsIgnoreCase(AttributeBuilder.build("name", "John")); + DatabaseFilterTranslator tr = getDatabaseFilterTranslator(); + List blist = tr.translate(f); + assertEquals(1, blist.size()); + final FilterWhereBuilder b = blist.get(0); + assertEquals("LOWER(name) = LOWER( ? )", b.getWhereClause()); + } + + private DatabaseFilterTranslator getDatabaseFilterTranslator() { + return new DatabaseFilterTranslator(ObjectClass.ACCOUNT, null) { + + @Override + protected SQLParam getSQLParam(Attribute attribute, ObjectClass oclass, OperationOptions options) { + return new SQLParam(attribute.getName(), AttributeUtil.getSingleValue(attribute), Types.NULL); + } + }; + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseQueryBuilderTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseQueryBuilderTest.java new file mode 100644 index 00000000..3ddf590b --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/DatabaseQueryBuilderTest.java @@ -0,0 +1,212 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import net.tirasa.connid.commons.db.DatabaseQueryBuilder.OrderBy; +import org.junit.jupiter.api.Test; + +/** + * DatabaseQueryBuilder test Class + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class DatabaseQueryBuilderTest { + + private static final String SELECT = "SELECT * FROM Users"; + + private static final String SELECT_WITH_WHERE = "SELECT * FROM Users WHERE test = 1"; + + private static final SQLParam VALUE = new SQLParam("value", "value"); + + private static final String NAME = "name"; + + private static final String OPERATOR = "="; + + /** + * Test method for {@link DatabaseQueryBuilder#DatabaseQueryBuilder(String, Set)}. + */ + @Test + public void testFilterQueryBuilderTableMissing() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseQueryBuilder("", null).getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#DatabaseQueryBuilder(String, Set)}. + */ + @Test + public void testFilterQueryBuilderColumnMissing() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseQueryBuilder("table", null).getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#DatabaseQueryBuilder(String, Set)}. + */ + @Test + public void testFilterQueryBuilderColumnEmpty() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseQueryBuilder("table", new HashSet<>()).getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#DatabaseQueryBuilder(String)}. + */ + @Test + public void testFilterQueryBuilderSelectMissing() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseQueryBuilder("").getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#DatabaseQueryBuilder(String)}. + */ + @Test + public void testFilterQueryBuilderWhereMissing() { + assertThrows( + IllegalArgumentException.class, + () -> new DatabaseQueryBuilder(SELECT.substring(0, 7), null).getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#DatabaseQueryBuilder(String)}. + */ + @Test + public void testFilterQueryBuilder() { + DatabaseQueryBuilder actual = new DatabaseQueryBuilder(SELECT); + assertNotNull(actual); + assertNotNull(actual.getSQL()); + assertEquals(SELECT, actual.getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#getSQL()}. + */ + @Test + public void testGetSql() { + FilterWhereBuilder where = new FilterWhereBuilder(); + final SQLParam param = new SQLParam(NAME, VALUE); + where.addBind(param, OPERATOR, false); + DatabaseQueryBuilder actual = new DatabaseQueryBuilder(SELECT); + actual.setWhere(where); + assertNotNull(actual); + assertEquals(SELECT + " WHERE name = ?", actual.getSQL()); + assertEquals(1, actual.getParams().size()); + assertEquals(param, actual.getParams().get(0)); + } + + /** + * Test method for {@link DatabaseQueryBuilder#getSQL()}. + */ + @Test + public void testGetSqlWithWhere() { + FilterWhereBuilder where = new FilterWhereBuilder(); + final SQLParam param = new SQLParam(NAME, VALUE); + where.addBind(param, OPERATOR, false); + DatabaseQueryBuilder actual = new DatabaseQueryBuilder(SELECT_WITH_WHERE); + actual.setWhere(where); + assertNotNull(actual); + assertEquals("SELECT * FROM Users WHERE ( test = 1) AND ( name = ? )", actual.getSQL()); + assertEquals(1, actual.getParams().size()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#getSQL()}. + */ + @Test + public void testGetSqlWithEmptyWhere() { + FilterWhereBuilder where = new FilterWhereBuilder(); + DatabaseQueryBuilder actual = new DatabaseQueryBuilder(SELECT_WITH_WHERE); + actual.setWhere(where); + assertNotNull(actual); + assertEquals(SELECT_WITH_WHERE, actual.getSQL()); + } + + /** + * Test method for {@link DatabaseQueryBuilder#getSQL()}. + */ + @Test + public void testGetSqlWithAttributesToGet() { + Set attributesToGet = new LinkedHashSet<>(); + attributesToGet.add("test1"); + attributesToGet.add("test2"); + FilterWhereBuilder where = new FilterWhereBuilder(); + final SQLParam param = new SQLParam(NAME, VALUE); + where.addBind(param, OPERATOR, false); + DatabaseQueryBuilder actual = new DatabaseQueryBuilder("table", attributesToGet); + actual.setWhere(where); + assertEquals("SELECT test1 , test2 FROM table WHERE name = ?", actual.getSQL()); + assertEquals(1, actual.getParams().size()); + assertEquals(param, actual.getParams().get(0)); + } + + /** + * Test method for {@link DatabaseQueryBuilder#getSQL()}. + */ + @Test + public void testGetSqlWithAttributesToGetDifferentQuoting() { + Set attributesToGet = new LinkedHashSet<>(); + attributesToGet.add("test1"); + attributesToGet.add("test2"); + FilterWhereBuilder where = new FilterWhereBuilder(); + final SQLParam param = new SQLParam(NAME, VALUE); + where.addBind(param, OPERATOR, false); + DatabaseQueryBuilder actual = new DatabaseQueryBuilder("table", attributesToGet); + actual.setWhere(where); + actual.setTableName("table"); + assertNotNull(actual); + assertEquals("SELECT test1 , test2 FROM table WHERE name = ?", actual.getSQL()); + assertEquals(1, actual.getParams().size()); + assertEquals(param, actual.getParams().get(0)); + } + + /** + * Test method for {@link DatabaseQueryBuilder#getSQL()}. + */ + @Test + public void testGetSqlWithAttributesToGetAndOrderBy() { + Set attributesToGet = new LinkedHashSet<>(); + List orderBy = new ArrayList<>(); + attributesToGet.add("test1"); + attributesToGet.add("test2"); + orderBy.add(new OrderBy("test1", true)); + orderBy.add(new OrderBy("test2", false)); + FilterWhereBuilder where = new FilterWhereBuilder(); + final SQLParam param = new SQLParam(NAME, VALUE); + where.addBind(param, OPERATOR, false); + DatabaseQueryBuilder actual = new DatabaseQueryBuilder("table", attributesToGet); + actual.setWhere(where); + actual.setOrderBy(orderBy); + assertNotNull(actual); + assertEquals("SELECT test1 , test2 FROM table WHERE name = ? ORDER BY test1 ASC, test2 DESC", actual.getSQL()); + assertEquals(1, actual.getParams().size()); + assertEquals(param, actual.getParams().get(0)); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/FilterWhereBuilderTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/FilterWhereBuilderTest.java new file mode 100644 index 00000000..a4046907 --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/FilterWhereBuilderTest.java @@ -0,0 +1,150 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * FilterWhereBuilder test class + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class FilterWhereBuilderTest { + + private static final SQLParam VALUE = new SQLParam("value", "value"); + + private static final String NAME = "name"; + + private static final String OPERATOR = "="; + + /** + * Test method for {@link FilterWhereBuilder#FilterWhereBuilder(String, java.util.Map)}. + */ + @Test + public void testFilterQueryBuilder() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + } + + /** + * Test method for {@link FilterWhereBuilder#join(String, FilterWhereBuilder, FilterWhereBuilder)}. + */ + @Test + public void testJoin() { + FilterWhereBuilder l = new FilterWhereBuilder(); + final SQLParam param = new SQLParam(NAME, VALUE); + l.addBind(param, OPERATOR, false); + FilterWhereBuilder r = new FilterWhereBuilder(); + r.addBind(param, OPERATOR, false); + FilterWhereBuilder actual = new FilterWhereBuilder(); + actual.join("AND", l, r); + assertNotNull(actual); + assertNotNull(actual.getParams()); + assertTrue(actual.getParams().contains(param)); + assertEquals(2, actual.getParams().size()); + assertEquals("name = ? AND name = ?", actual.getWhereClause()); + } + + /** + * Test method for {@link FilterWhereBuilder#getNames()}. + */ + @Test + public void testGetNamesAndValues() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + assertNotNull(actual.getParams()); + final SQLParam param = new SQLParam(NAME, VALUE); + actual.addBind(param, OPERATOR, false); + assertTrue(actual.getParams().contains(param)); + } + + /** + * Test method for {@link FilterWhereBuilder#getWhere()}. + */ + @Test + public void testGetWhere() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + final SQLParam param = new SQLParam(NAME, VALUE); + actual.addBind(param, OPERATOR, false); + assertNotNull(actual.getWhere()); + assertEquals("name = ?", actual.getWhere().toString()); + } + + /** + * Test method for {@link FilterWhereBuilder#getWhere()}. + */ + @Test + public void testEmptyWhere() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + assertNotNull(actual.getWhere()); + assertEquals("", actual.getWhere().toString()); + } + + /** + * Test method for {@link FilterWhereBuilder#addBind(String, String, Object)}. + */ + @Test + public void testAddBind() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + assertNotNull(actual.getParams()); + final SQLParam param = new SQLParam(NAME, VALUE); + actual.addBind(param, OPERATOR, false); + assertTrue(actual.getParams().contains(param)); + assertEquals("name = ?", actual.getWhereClause()); + } + + /** + * Test method for {@link FilterWhereBuilder#getWhereClause()}. + */ + @Test + public void testGetWhereClause() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + final SQLParam param = new SQLParam(NAME, VALUE); + actual.addBind(param, OPERATOR, false); + assertEquals("name = ?", actual.getWhereClause()); + assertEquals(1, actual.getParams().size()); + } + + /** + * Test method for {@link FilterWhereBuilder#getWhereClause()}. + */ + @Test + public void testGetWhereClauseWithWhere() { + FilterWhereBuilder actual = new FilterWhereBuilder(); + assertNotNull(actual); + final SQLParam param = new SQLParam(NAME, VALUE); + actual.addBind(param, OPERATOR, false); + assertEquals("name = ?", actual.getWhereClause()); + assertEquals(1, actual.getParams().size()); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/InsertIntoBuilderTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/InsertIntoBuilderTest.java new file mode 100644 index 00000000..09c4b3b3 --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/InsertIntoBuilderTest.java @@ -0,0 +1,93 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.sql.Types; +import org.junit.jupiter.api.Test; + +/** + * Tests + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class InsertIntoBuilderTest { + + /** + * Test method for {@link org.identityconnectors.dbcommon.InsertIntoBuilder#InsertIntoBuilder()}. + */ + @Test + public void testInsertIntoBuilder() { + InsertIntoBuilder actual = new InsertIntoBuilder(); + assertNotNull(actual); + assertNotNull(actual.getParams()); + assertNotNull(actual.getValues()); + assertNotNull(actual.getInto()); + assertEquals("", actual.getValues()); + assertEquals("", actual.getInto()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.InsertIntoBuilder#addBind(String, Object)}. + */ + @Test + public void testAddBindExpression() { + InsertIntoBuilder actual = new InsertIntoBuilder(); + assertNotNull(actual); + // do the update + actual.addBind(new SQLParam("test1", "val1")); + assertNotNull(actual.getInto()); + assertEquals("The update string", "test1", actual.getInto()); + assertNotNull(actual.getValues()); + assertEquals("The update string", "?", actual.getValues()); + assertNotNull(actual.getParams()); + assertEquals(1, actual.getParams().size()); + assertEquals("val1", actual.getParams().get(0).getValue()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.InsertIntoBuilder#addBind(String, Object)}. + */ + @Test + public void testAddSecondBindExpression() { + InsertIntoBuilder actual = new InsertIntoBuilder(); + assertNotNull(actual); + // do the update + actual.addBind(new SQLParam("test1", "val1")); + actual.addBind(new SQLParam("test2", "val2", Types.VARCHAR)); + assertNotNull(actual.getInto()); + assertEquals("The update string", "test1, test2", actual.getInto()); + assertNotNull(actual.getValues()); + assertEquals("The update string", "?, ?", actual.getValues()); + assertNotNull(actual.getParams()); + assertEquals(2, actual.getParams().size()); + assertEquals("val1", actual.getParams().get(0).getValue()); + assertEquals("val2", actual.getParams().get(1).getValue()); + assertEquals(Types.NULL, actual.getParams().get(0).getSqlType()); + assertEquals(Types.VARCHAR, actual.getParams().get(1).getSqlType()); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/JNDIUtilTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/JNDIUtilTest.java new file mode 100644 index 00000000..1b4d3b5b --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/JNDIUtilTest.java @@ -0,0 +1,89 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * @author kitko + * + */ +public class JNDIUtilTest { + + @Test + public void testArrayToHashtableSuc() { + String[] entries1 = { "a=A", "b=B" }; + Map res1 = new HashMap<>(); + res1.put("a", "A"); + res1.put("b", "B"); + assertEquals(res1, JNDIUtil.arrayToProperties(entries1, null)); + } + + /** + * test for testArrayToHashtable fail + */ + @Test + public void testArrayToHashtableFail() { + try { + String[] entries2 = { "a=A", "b=" }; + JNDIUtil.arrayToProperties(entries2, null); + fail(); + } catch (RuntimeException e) { + //expected + } + try { + String[] entries2 = { "a=A", "=" }; + JNDIUtil.arrayToProperties(entries2, null); + fail(); + } catch (RuntimeException e) { + //expected + } + try { + String[] entries2 = { "a=A", "=B" }; + JNDIUtil.arrayToProperties(entries2, null); + fail(); + } catch (RuntimeException e) { + //expected + } + } + + /** + * test for testArrayToHashtable fail + */ + @Test + public void testArrayToHashtableNull() { + JNDIUtil.arrayToProperties(null, null); + String[] entries2 = {}; + JNDIUtil.arrayToProperties(entries2, null); + String[] entries3 = { null, null }; + JNDIUtil.arrayToProperties(entries3, null); + String[] entries4 = { "", " " }; + JNDIUtil.arrayToProperties(entries4, null); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/LocalizedAssertTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/LocalizedAssertTest.java new file mode 100644 index 00000000..fa8bd56b --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/LocalizedAssertTest.java @@ -0,0 +1,176 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Properties; +import org.identityconnectors.common.IOUtil; +import org.identityconnectors.framework.common.objects.ConnectorMessages; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** Tests for LocalizedAssert */ +public class LocalizedAssertTest { + + private static class TestConnectorMessages implements ConnectorMessages { + + private Properties properties; + + TestConnectorMessages() { + try { + properties = IOUtil.getResourceAsProperties(getClass() + .getClassLoader(), LocalizedAssertTest.class + .getPackage().getName().replace('.', '/') + + "/Messages.properties"); + assertNotNull(properties); + } catch (IOException e) { + fail("Cannot load Messages.properties" + e.getMessage()); + } + } + + @Override + public String format(String key, String dflt, Object... args) { + String value = properties.getProperty(key); + if (value == null) { + return dflt != null ? dflt : key; + } + if (args == null) { + return value; + } + return MessageFormat.format(value, args); + } + } + + static LocalizedAssert testee; + + @BeforeAll + public static void setup() { + testee = new LocalizedAssert(new TestConnectorMessages()); + } + + /** + * Test method for + * {@link org.identityconnectors.dbcommon.LocalizedAssert#assertNotNull(java.lang.Object, java.lang.String)}. + */ + @Test + public final void testAssertNotNull() { + Integer i = testee.assertNotNull(1, "i"); + assertEquals(1, i); + try { + testee.assertNotNull(null, "i"); + fail("Must fail for null argument"); + } catch (RuntimeException e) { + assertEquals("Argument [i] cannot be null", e.getMessage()); + } + } + + /** + * Test method for + * {@link org.identityconnectors.dbcommon.LocalizedAssert#assertNull(java.lang.Object, java.lang.String)}. + */ + @Test + public final void testAssertNull() { + Integer i = testee.assertNull(null, "i"); + assertNull(i); + try { + testee.assertNull(1, "i"); + fail("Must fail for not null argument"); + } catch (RuntimeException e) { + assertEquals("Argument [i] must be null", e.getMessage()); + } + } + + /** + * Test method for + * {@link org.identityconnectors.dbcommon.LocalizedAssert#assertNotBlank(java.lang.String, java.lang.String)}. + */ + @Test + public final void testAssertNotBlank() { + String os = testee.assertNotBlank("Linux", "os"); + assertEquals("Linux", os); + try { + testee.assertNotBlank(null, "os"); + fail("Must fail for null argument"); + } catch (RuntimeException e) { + assertEquals("Argument [os] cannot be blank", e.getMessage()); + } + try { + testee.assertNotBlank("", "os"); + fail("Must fail for blank argument"); + } catch (RuntimeException e) { + assertEquals("Argument [os] cannot be blank", e.getMessage()); + } + } + + /** + * Test method for + * {@link org.identityconnectors.dbcommon.LocalizedAssert#assertBlank(java.lang.String, java.lang.String)}. + */ + @Test + public final void testAsserBlank() { + String os = testee.assertBlank(null, "os"); + assertNull(os); + os = testee.assertBlank("", "os"); + assertEquals("", os); + try { + testee.assertBlank("Some os", "os"); + fail("Must fail for non blank argument"); + } catch (RuntimeException e) { + assertEquals("Argument [os] must be blank", e.getMessage()); + } + } + + /** Test of {@link LocalizedAssert#LocalizedAssert(ConnectorMessages)} */ + @Test + public void testCreate() { + new LocalizedAssert(new TestConnectorMessages()); + try { + new LocalizedAssert(null); + fail("Must fail for null ConnectorMessages"); + } catch (RuntimeException e) { + //emptyS + } + } + + /** + * test method + */ + @Test + public void testLocalizeArgumentNames() { + LocalizedAssert la = new LocalizedAssert(new TestConnectorMessages(), true); + try { + //Small hack, we do not create new TestMessages with dummy argument name, use assert.blank as argument name + la.assertNotBlank("", "assert.blank"); + fail("Must fail for blank String"); + } catch (RuntimeException e) { + assertEquals("Argument [Argument [{0}] must be blank] cannot be blank", e.getMessage()); + } + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/PropertiesResolverTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/PropertiesResolverTest.java new file mode 100644 index 00000000..83d6ffef --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/PropertiesResolverTest.java @@ -0,0 +1,97 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.Test; + +/** + * Test for {@link PropertiesResolver} + * + * @author kitko + * + */ +public class PropertiesResolverTest { + + @Test + public void testResolvePropertiesPropertiesProperties() { + Properties p1 = new Properties(); + p1.setProperty("key1", "value1"); + p1.setProperty("key2", "value2"); + p1.setProperty("key7", "${key1}"); + Map p1Copy = new HashMap<>(p1); + Properties p2 = new Properties(); + p2.setProperty("key3", "${key1}"); + p2.setProperty("key4", "${key2}"); + Map p2Copy = new HashMap<>(p2); + Properties p3 = PropertiesResolver.resolveProperties(p2, p1); + assertEquals(p1Copy, p1); + assertEquals(p2Copy, p2); + assertEquals("value1", p3.getProperty("key3")); + assertEquals("value2", p3.getProperty("key4")); + } + + /** + * Simple test + * + * @throws InterruptedException + */ + @Test + public void testSimpleResolveProperties() throws InterruptedException { + Properties properties = new Properties(); + properties.setProperty("key1", "value1"); + properties.setProperty("key2", "Value of key1 is ${key1}"); + properties.setProperty("key3", "Reference ${key4}"); + properties = PropertiesResolver.resolveProperties(properties); + assertEquals("Value of key1 is value1", properties.get("key2")); + assertEquals("Reference ${key4}", properties.get("key3")); + } + + @Test + public void testAdvancedResolved() { + Properties properties = new Properties(); + properties.setProperty("key1", "value1"); + properties.setProperty("key2", "${key1}"); + properties.setProperty("key3", "value3"); + properties.setProperty("key4", "${key2} ${key3}"); + properties = PropertiesResolver.resolveProperties(properties); + assertEquals("value1 value3", properties.get("key4")); + } + + /** Test that we will not fail on StackOverflowError */ + @Test + public void testRecursion() { + Properties properties = new Properties(); + properties.setProperty("key1", "value1 ${key3}"); + properties.setProperty("key2", "value2 ${key1}"); + properties.setProperty("key3", "value3 ${key2}"); + properties = PropertiesResolver.resolveProperties(properties); + System.out.println(properties.get("key3")); + assertEquals("value3 value2 value1 RECURSION", properties.get("key3")); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLParamTests.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLParamTests.java new file mode 100644 index 00000000..ed84f6da --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLParamTests.java @@ -0,0 +1,90 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.Types; +import org.junit.jupiter.api.Test; + +/** + * The SQL util tests + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class SQLParamTests { + + /** + * Test method + */ + @Test + public void paramCreateValue() { + SQLParam a = new SQLParam("A", "B", 5); + assertEquals("A", a.getName()); + assertEquals("B", a.getValue()); + assertEquals(5, a.getSqlType()); + } + + /** + * Test method + */ + @Test + public void paramCreateNoSQLType() { + SQLParam a = new SQLParam("A", "B"); + assertEquals("A", a.getName()); + assertEquals("B", a.getValue()); + } + + /** + * Test method + */ + @Test + public void paramTestHashEqual2() { + SQLParam a = new SQLParam("A", "B"); + SQLParam b = new SQLParam("A", "B"); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a, b); + } + + /** + * Test method + */ + @Test + public void paramTestHashEqual3() { + SQLParam a = new SQLParam("A", "B", Types.VARCHAR); + SQLParam b = new SQLParam("A", "B", Types.VARCHAR); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a, b); + } + + /** + * Test method + */ + @Test + public void toStringTest() { + SQLParam a = new SQLParam("A", "B", Types.VARCHAR); + assertEquals("A=\"B\":[VARCHAR]", a.toString()); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLUtilTests.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLUtilTests.java new file mode 100644 index 00000000..1c927fcc --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/SQLUtilTests.java @@ -0,0 +1,514 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.math.BigDecimal; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; +import javax.sql.DataSource; +import org.identityconnectors.common.security.GuardedString; +import org.junit.jupiter.api.Test; + +/** + * + * The SQL util tests. + */ +public class SQLUtilTests { + + /** + * Test method for {@link SQLUtil#closeQuietly(Connection)}. + * + * @throws Exception + */ + @Test + public void quietConnectionClose() throws Exception { + ExpectProxy tp = new ExpectProxy<>(); + tp.expectAndReturn("isClosed", Boolean.FALSE); + tp.expectAndThrow("close", new SQLException("expected")); + Connection c = tp.getProxy(Connection.class); + DatabaseConnection dbc = new DatabaseConnection(c); + SQLUtil.closeQuietly(dbc); + assertTrue(tp.isDone()); + tp = new ExpectProxy<>(); + tp.expectAndReturn("isClosed", Boolean.TRUE); + c = tp.getProxy(Connection.class); + dbc = new DatabaseConnection(c); + SQLUtil.closeQuietly(dbc); + assertTrue(tp.isDone()); + //null tests + dbc = null; + SQLUtil.closeQuietly(dbc); + } + + /** + * Test method for {@link SQLUtil#rollbackQuietly(Connection)}. + * + * @throws Exception + */ + @Test + public void quietConnectionRolback() throws Exception { + ExpectProxy tp = new ExpectProxy<>(); + tp.expectAndReturn("isClosed", Boolean.FALSE); + tp.expectAndThrow("rollback", new SQLException("expected")); + Connection s = tp.getProxy(Connection.class); + DatabaseConnection dbc = new DatabaseConnection(s); + SQLUtil.rollbackQuietly(dbc); + assertTrue(tp.isDone()); + tp = new ExpectProxy<>(); + tp.expectAndReturn("isClosed", Boolean.TRUE); + s = tp.getProxy(Connection.class); + dbc = new DatabaseConnection(s); + SQLUtil.rollbackQuietly(dbc); + assertTrue(tp.isDone()); + //null tests + dbc = null; + SQLUtil.rollbackQuietly(dbc); + } + + /** + * Test method for {@link SQLUtil#closeQuietly(Statement)}. + */ + @Test + public void quietStatementClose() { + ExpectProxy tp = new ExpectProxy().expectAndThrow("close", new SQLException("expected")); + Statement s = tp.getProxy(Statement.class); + SQLUtil.closeQuietly(s); + assertTrue(tp.isDone()); + } + + /** + * Test method for {@link SQLUtil#closeQuietly(ResultSet)}. + */ + @Test + public void quietResultSetClose() { + ExpectProxy tp = new ExpectProxy().expectAndThrow("close", new SQLException("expected")); + ResultSet c = tp.getProxy(ResultSet.class); + SQLUtil.closeQuietly(c); + assertTrue(tp.isDone()); + } + + /** + * Test method for {@link SQLUtil#closeQuietly(Connection)}. + */ + @Test + public void withNull() { + // attempt to close on a null.. + SQLUtil.closeQuietly((Connection) null); + SQLUtil.closeQuietly((ResultSet) null); + SQLUtil.closeQuietly((Statement) null); + } + + @Test + public void testBuildConnectorObjectBuilderNull() throws SQLException { + assertThrows(NullPointerException.class, () -> SQLUtil.getColumnValues(null)); + } + + @Test + public void testConvertBlob() throws SQLException { + ExpectProxy tb = new ExpectProxy<>(); + byte[] expected = new byte[] { 'a', 'h', 'o', 'j' }; + final ByteArrayInputStream is = new ByteArrayInputStream(expected); + tb.expectAndReturn("getBinaryStream", is); + Blob blob = tb.getProxy(Blob.class); + final Object object = SQLUtil.jdbc2AttributeValue(blob); + assertEquals(expected[0], ((byte[]) object)[0]); + assertEquals(expected[3], ((byte[]) object)[3]); + assertTrue(tb.isDone()); + } + + @Test + public void testConvertBooleanToAttribute() throws SQLException { + String expected = "test"; + final Object object = SQLUtil.jdbc2AttributeValue(expected); + assertEquals(expected, object); + } + + @Test + public void testConvertDateToAttribute() throws SQLException { + java.sql.Date src = new java.sql.Date(System.currentTimeMillis()); + final Object object = SQLUtil.jdbc2AttributeValue(src); + assertEquals(src.toString(), object); + } + + @Test + public void testString2Timestamp() throws SQLException { + final String src = "2008-12-31 23:59:59.999999999"; + final Timestamp timestamp = SQLUtil.string2Timestamp(src); + assertEquals(src, SQLUtil.timestamp2String(timestamp)); + } + + @Test + public void testString2Date() throws SQLException { + final String src = "2008-12-31"; + final Date date = SQLUtil.string2Date(src); + assertEquals(src, SQLUtil.date2String(date)); + } + + @Test + public void testString2Time() throws SQLException { + final String src = "23:59:59"; + final Time time = SQLUtil.string2Time(src); + assertEquals(src, SQLUtil.time2String(time)); + } + + @Test + public void testConvertTimestampToAttribute() throws SQLException { + Timestamp src = new Timestamp(System.currentTimeMillis()); + final Object object = SQLUtil.jdbc2AttributeValue(src); + assertEquals(src.toString(), object); + } + + @Test + public void testConvertTimestampToJDBC() { + Timestamp expected = new Timestamp(System.currentTimeMillis()); + String src = SQLUtil.timestamp2String(expected); + Object actual = SQLUtil.string2Timestamp(src); + assertNotNull(actual); + assertEquals(expected.getClass(), actual.getClass()); + assertEquals(expected, actual); + } + + @Test + public void testConvertSqlDateToJDBC() { + Date expected = new Date(System.currentTimeMillis()); + String src = SQLUtil.date2String(expected); + Object actual = SQLUtil.string2Date(src); + assertNotNull(actual); + assertEquals(expected.getClass(), actual.getClass()); + assertEquals(expected.toString(), actual.toString()); + } + + @Test + public void testNormalizeNullValues() { + final String sql = "insert into table values(?, ?, ?)"; + final List params = new ArrayList<>(); + params.add(new SQLParam("test", "test")); + params.add(new SQLParam("null", null)); //Null unspecified should be normalized + params.add(new SQLParam("null2", null, Types.VARCHAR)); //Null typed should remain + final List out = new ArrayList<>(); + String actual = SQLUtil.normalizeNullValues(sql, params, out); + assertNotNull(actual); + assertEquals("insert into table values(?, null, ?)", actual); + assertEquals(2, out.size()); + } + + @Test + public void testNormalizeNullValuesSame() { + final String sql = "insert into table values(?, ?, ?)"; + final List params = new ArrayList<>(); + params.add(new SQLParam("test1", "test")); + params.add(new SQLParam("test2", 1)); + params.add(new SQLParam("test3", 5, Types.VARCHAR)); + final List out = new ArrayList<>(); + String actual = SQLUtil.normalizeNullValues(sql, params, out); + assertNotNull(actual); + assertEquals("insert into table values(?, ?, ?)", actual); + assertEquals(3, out.size()); + } + + @Test + public void testNormalizeNullValuesLess() { + final String sql = "insert into table values(?, ?, ?)"; + final List params = new ArrayList<>(); + params.add(new SQLParam("test1", "test")); + params.add(new SQLParam("test2", 3)); + final List out = new ArrayList<>(); + try { + SQLUtil.normalizeNullValues(sql, params, out); + fail("IllegalStateException expected"); + } catch (IllegalStateException expected) { + // expected + } + params.add(new SQLParam("test3", 3)); + params.add(new SQLParam("test4", 3)); + try { + SQLUtil.normalizeNullValues(sql, params, out); + fail("IllegalStateException expected"); + } catch (IllegalStateException expected) { + // expected + } + } + + /** + * GetAttributeSet test method. + * + * @throws SQLException * + */ + @Test + public void testGetAttributeSet() throws SQLException { + final String TEST1 = "test1"; + final String TEST_VAL1 = "testValue1"; + final String TEST2 = "test2"; + final String TEST_VAL2 = "testValue2"; + //Resultset + final ExpectProxy trs = new ExpectProxy<>(); + ResultSet resultSetProxy = trs.getProxy(ResultSet.class); + //Metadata + final ExpectProxy trsmd = new ExpectProxy<>(); + ResultSetMetaData metaDataProxy = trsmd.getProxy(ResultSetMetaData.class); + trs.expectAndReturn("getMetaData", metaDataProxy); + trsmd.expectAndReturn("getColumnCount", 2); + trsmd.expectAndReturn("getColumnName", TEST1); + trsmd.expectAndReturn("getColumnType", Types.VARCHAR); + trs.expectAndReturn("getString", TEST_VAL1); + trsmd.expectAndReturn("getColumnName", TEST2); + trsmd.expectAndReturn("getColumnType", Types.VARCHAR); + trs.expectAndReturn("getString", TEST_VAL2); + final Map actual = SQLUtil.getColumnValues(resultSetProxy); + assertTrue(trs.isDone()); + assertTrue(trsmd.isDone()); + assertEquals(2, actual.size()); + assertNotNull(actual.get(TEST1)); + assertNotNull(actual.get(TEST2)); + assertEquals(TEST_VAL1, actual.get(TEST1).getValue()); + assertEquals(TEST_VAL2, actual.get(TEST2).getValue()); + } + + /** + * GetAttributeSet test method. + * + * @throws SQLException * + */ + @Test + public void testGetSQLParam() throws SQLException { + final String TEST_STR = "testValue1"; + final Timestamp TEST_TMS = new Timestamp(System.currentTimeMillis()); + final Date TEST_DATE = new Date(System.currentTimeMillis()); + final Time TEST_TIME = new Time(System.currentTimeMillis()); + //Resultset + final ExpectProxy trs = new ExpectProxy<>(); + ResultSet resultSetProxy = trs.getProxy(ResultSet.class); + trs.expectAndReturn("getObject", TEST_STR); + SQLParam actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.NULL); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_STR, actual.getValue()); + trs.expectAndReturn("getString", TEST_STR); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.VARCHAR); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_STR, actual.getValue()); + trs.expectAndReturn("getObject", TEST_STR); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.DOUBLE); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_STR, actual.getValue()); + trs.expectAndReturn("getObject", TEST_STR); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.BLOB); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_STR, actual.getValue()); + trs.expectAndReturn("getTimestamp", TEST_TMS); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.TIMESTAMP); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_TMS, actual.getValue()); + trs.expectAndReturn("getDate", TEST_DATE); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.DATE); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_DATE, actual.getValue()); + trs.expectAndReturn("getTime", TEST_TIME); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.TIME); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(TEST_TIME, actual.getValue()); + trs.expectAndReturn("getBoolean", Boolean.TRUE); + actual = SQLUtil.getSQLParam(resultSetProxy, 0, TEST_STR, Types.BOOLEAN); + assertTrue(trs.isDone()); + assertNotNull(actual); + assertEquals(Boolean.TRUE, actual.getValue()); + } + + /** + * GetAttributeSet test method. + * + * @throws SQLException * + */ + @Test + public void testSetSQLParam() throws SQLException { + final String TEST_STR = "testValue1"; + final Timestamp TEST_TMS = new Timestamp(System.currentTimeMillis()); + final Date TEST_DATE = new Date(System.currentTimeMillis()); + final Time TEST_TIME = new Time(System.currentTimeMillis()); + //Resultset + final ExpectProxy trs = new ExpectProxy<>(); + PreparedStatement resultSetProxy = trs.getProxy(PreparedStatement.class); + trs.expect("setNull"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, null, Types.CHAR)); + assertTrue(trs.isDone()); + trs.expect("setObject"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, TEST_STR)); + assertTrue(trs.isDone()); + trs.expect("setString"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, TEST_STR, Types.CHAR)); + assertTrue(trs.isDone()); + trs.expect("setBoolean"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, Boolean.TRUE, Types.BOOLEAN)); + assertTrue(trs.isDone()); + trs.expect("setTimestamp"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, TEST_TMS, Types.TIMESTAMP)); + assertTrue(trs.isDone()); + trs.expect("setTime"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, TEST_TIME, Types.TIME)); + assertTrue(trs.isDone()); + trs.expect("setDate"); + SQLUtil.setSQLParam(resultSetProxy, 0, new SQLParam(TEST_STR, TEST_DATE, Types.DATE)); + assertTrue(trs.isDone()); + } + + /** + * GetAttributeSet test method. + */ + @Test + public void testGetSQLAttributeType() { + assertEquals(BigDecimal.class, SQLUtil.getSQLAttributeType(Types.DECIMAL)); + assertEquals(BigDecimal.class, SQLUtil.getSQLAttributeType(Types.NUMERIC)); + assertEquals(Double.class, SQLUtil.getSQLAttributeType(Types.DOUBLE)); + assertEquals(BigDecimal.class, SQLUtil.getSQLAttributeType(Types.NUMERIC)); + assertEquals(Float.class, SQLUtil.getSQLAttributeType(Types.FLOAT)); + assertEquals(Float.class, SQLUtil.getSQLAttributeType(Types.REAL)); + assertEquals(Integer.class, SQLUtil.getSQLAttributeType(Types.INTEGER)); + assertEquals(Long.class, SQLUtil.getSQLAttributeType(Types.BIGINT)); + assertEquals(Byte.class, SQLUtil.getSQLAttributeType(Types.TINYINT)); + assertEquals(byte[].class, SQLUtil.getSQLAttributeType(Types.BLOB)); + assertEquals(byte[].class, SQLUtil.getSQLAttributeType(Types.BINARY)); + assertEquals(byte[].class, SQLUtil.getSQLAttributeType(Types.LONGVARBINARY)); + assertEquals(byte[].class, SQLUtil.getSQLAttributeType(Types.VARBINARY)); + assertEquals(Boolean.class, SQLUtil.getSQLAttributeType(Types.BIT)); + assertEquals(Boolean.class, SQLUtil.getSQLAttributeType(Types.BOOLEAN)); + assertEquals(String.class, SQLUtil.getSQLAttributeType(Types.CHAR)); + assertEquals(String.class, SQLUtil.getSQLAttributeType(Types.CLOB)); + assertEquals(String.class, SQLUtil.getSQLAttributeType(Types.VARCHAR)); + } + + /** + * GetAttributeSet test method. + * + * @throws SQLException * + */ + @Test + public void testJdbc2Attribute() throws SQLException { + final String TEST_STR = "testValue1"; + final Timestamp TEST_TMS = new Timestamp(System.currentTimeMillis()); + final Date TEST_DATE = new Date(System.currentTimeMillis()); + final Time TEST_TIME = new Time(System.currentTimeMillis()); + Object actual = SQLUtil.jdbc2AttributeValue(TEST_STR); + assertEquals(TEST_STR, actual); + actual = SQLUtil.jdbc2AttributeValue(TEST_TMS); + assertEquals(TEST_TMS.toString(), actual); + actual = SQLUtil.jdbc2AttributeValue(TEST_DATE); + assertEquals(TEST_DATE.toString(), actual); + actual = SQLUtil.jdbc2AttributeValue(TEST_TIME); + assertEquals(TEST_TIME.toString(), actual); + actual = SQLUtil.jdbc2AttributeValue(1); + assertEquals(1, actual); + actual = SQLUtil.jdbc2AttributeValue(1L); + assertEquals(1L, actual); + actual = SQLUtil.jdbc2AttributeValue(1d); + assertEquals(1d, actual); + actual = SQLUtil.jdbc2AttributeValue(1f); + assertEquals(1f, actual); + actual = SQLUtil.jdbc2AttributeValue(true); + assertEquals(true, actual); + } + + /** + * GetAttributeSet test method. + * + * @throws SQLException * + */ + @Test + public void testAttribute2JdbcValue() throws SQLException { + final String TEST_STR = "testValue1"; + final Timestamp TEST_TMS = new Timestamp(System.currentTimeMillis()); + final Date TEST_DATE = new Date(System.currentTimeMillis()); + final Time TEST_TIME = new Time(System.currentTimeMillis()); + Object actual = SQLUtil.attribute2jdbcValue(TEST_STR, Types.CHAR); + assertEquals(TEST_STR, actual); + actual = SQLUtil.attribute2jdbcValue(TEST_TMS.toString(), Types.TIMESTAMP); + assertEquals(TEST_TMS, actual); + actual = SQLUtil.attribute2jdbcValue(TEST_TIME.toString(), Types.TIME); + assertEquals(TEST_TIME.toString(), actual.toString()); + actual = SQLUtil.attribute2jdbcValue(TEST_DATE.toString(), Types.DATE); + assertEquals(TEST_DATE.toString(), actual.toString()); + actual = SQLUtil.attribute2jdbcValue("55.55", Types.DOUBLE); + assertEquals(55.55d, actual); + actual = SQLUtil.attribute2jdbcValue("true", Types.BIT); + assertEquals(true, actual); + } + + /** + * We need this helper class as InitialContextFactory class name value to Hashtable into InitialContext. + * + * We must use instantiable classname and class must be accessible + */ + public static class MockContextFactory implements InitialContextFactory { + + @Override + public Context getInitialContext(final Hashtable environment) throws NamingException { + ExpectProxy dsProxy = new ExpectProxy<>(); + dsProxy.expectAndReturn("getConnection", new ExpectProxy().getProxy(Connection.class)); + ExpectProxy ctxProxy = new ExpectProxy<>(); + ctxProxy.expectAndReturn("lookup", dsProxy.getProxy(DataSource.class)); + return ctxProxy.getProxy(Context.class); + } + } + + /** + * Tests getting connection from dataSource. + */ + @Test + public void testGetConnectionFromDS() { + Properties properties = new Properties(); + properties.put("java.naming.factory.initial", MockContextFactory.class.getName()); + assertNotNull(SQLUtil.getDatasourceConnection( + "", "user", new GuardedString("password".toCharArray()), properties)); + assertNotNull(SQLUtil.getDatasourceConnection("", properties)); + } +} diff --git a/java/commons/db/src/test/java/net/tirasa/connid/commons/db/UpdateSetBuilderTest.java b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/UpdateSetBuilderTest.java new file mode 100644 index 00000000..009f5a6a --- /dev/null +++ b/java/commons/db/src/test/java/net/tirasa/connid/commons/db/UpdateSetBuilderTest.java @@ -0,0 +1,102 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + * Portions Copyrighted 2011 ConnId. + */ +package net.tirasa.connid.commons.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.sql.Types; +import org.junit.jupiter.api.Test; + +/** + * Tests + * + * @version $Revision 1.0$ + * @since 1.0 + */ +public class UpdateSetBuilderTest { + + private static final String MYSQL_USER_COLUMN = "User"; + + private static final SQLParam VALUE = new SQLParam(MYSQL_USER_COLUMN, "name", Types.VARCHAR); + + /** + * Test method for {@link org.identityconnectors.dbcommon.UpdateSetBuilder#UpdateSetBuilder()}. + */ + @Test + public void testUpdateSetBuilder() { + UpdateSetBuilder actual = new UpdateSetBuilder(); + assertNotNull(actual); + assertNotNull(actual.getParams()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.UpdateSetBuilder#addBind(String, String, Object)}. + */ + @Test + public void testAddBindExpression() { + UpdateSetBuilder actual = new UpdateSetBuilder(); + assertNotNull(actual); + // do the update + actual.addBind(new SQLParam("test1", "val1"), "password(?)"); + actual.addBind(new SQLParam("test2", "val2"), "max(?)"); + assertNotNull(actual.getSQL()); + assertEquals("test1 = password(?) , test2 = max(?)", actual.getSQL()); + assertNotNull(actual.getParams()); + assertEquals(2, actual.getParams().size()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.UpdateSetBuilder#getParams()}. + */ + @Test + public void testGetValues() { + UpdateSetBuilder actual = new UpdateSetBuilder(); + assertNotNull(actual); + // do the update + actual.addBind(VALUE); + assertNotNull(actual.getSQL()); + assertEquals("User = ?", actual.getSQL()); + assertNotNull(actual.getParams()); + assertNotNull(actual.getParams().get(0)); + assertEquals(VALUE, actual.getParams().get(0)); + assertEquals(Types.VARCHAR, actual.getParams().get(0).getSqlType()); + } + + /** + * Test method for {@link org.identityconnectors.dbcommon.UpdateSetBuilder#addBind(String, Object)} + */ + @Test + public void testAddBind() { + UpdateSetBuilder actual = new UpdateSetBuilder(); + assertNotNull(actual); + // do the update + actual.addBind(VALUE); + assertNotNull(actual.getParams()); + assertEquals(1, actual.getParams().size()); + assertNotNull(actual.getParams().get(0)); + assertEquals(VALUE, actual.getParams().get(0)); + assertEquals(MYSQL_USER_COLUMN + " = ?", actual.getSQL()); + } +} diff --git a/java/commons/pom.xml b/java/commons/pom.xml new file mode 100644 index 00000000..2963fe34 --- /dev/null +++ b/java/commons/pom.xml @@ -0,0 +1,48 @@ + + + + 4.0.0 + + + net.tirasa.connid + connid + 1.6.0.0-SNAPSHOT + + + commons + ConnId: Commons + + pom + + + ${basedir}/.. + + + + db + scripted + + diff --git a/java/commons/scripted/pom.xml b/java/commons/scripted/pom.xml new file mode 100644 index 00000000..a1f468e0 --- /dev/null +++ b/java/commons/scripted/pom.xml @@ -0,0 +1,79 @@ + + + + + 4.0.0 + + + net.tirasa.connid + connid + 1.6.0.0-SNAPSHOT + + + commons-scripted + ConnId: Commons Scripted + + jar + + + ${basedir}/../.. + + + + + net.tirasa.connid + connector-framework + ${project.version} + + + net.tirasa.connid + connector-framework-internal + ${project.version} + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + src/main/resources + + + ${parent.path} + META-INF + + LICENSE + + + + + + diff --git a/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConfiguration.java b/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConfiguration.java new file mode 100644 index 00000000..62129407 --- /dev/null +++ b/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConfiguration.java @@ -0,0 +1,396 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 ConnId. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +package net.tirasa.connid.commons.scripted; + +import java.io.File; +import org.identityconnectors.common.logging.Log; +import org.identityconnectors.framework.spi.AbstractConfiguration; +import org.identityconnectors.framework.spi.ConfigurationProperty; + +public abstract class AbstractScriptedConfiguration extends AbstractConfiguration { + + protected static final Log LOG = Log.getLog(AbstractScriptedConfiguration.class); + + /** + * Scripting language. + */ + private String scriptingLanguage = "GROOVY"; + + public String getScriptingLanguage() { + return scriptingLanguage; + } + + public void setScriptingLanguage(String value) { + this.scriptingLanguage = value; + } + + /** + * Should password be passed to scripts in clear text? + */ + private boolean clearTextPasswordToScript = true; + + @ConfigurationProperty(displayMessageKey = "clearTextPasswordToScript.display", + helpMessageKey = "clearTextPasswordToScript.help", order = 1) + public boolean getClearTextPasswordToScript() { + return clearTextPasswordToScript; + } + + public void setClearTextPasswordToScript(boolean value) { + this.clearTextPasswordToScript = value; + } + + /** + * By default, scripts are loaded and compiled when a connector instance ss created and initialized. + * Setting reloadScriptOnExecution to true will make the connector load and compile the script every time it is + * called. + * Use only for test/debug purpose since this can have a significant impact on performance. + */ + private boolean reloadScriptOnExecution = false; + + @ConfigurationProperty(displayMessageKey = "reloadScriptOnExecution.display", + helpMessageKey = "reloadScriptOnExecution.help", order = 2) + public boolean isReloadScriptOnExecution() { + return reloadScriptOnExecution; + } + + public void setReloadScriptOnExecution(boolean reloadScriptOnExecution) { + this.reloadScriptOnExecution = reloadScriptOnExecution; + } + + /** + * Create script string. + */ + private String createScript = ""; + + @ConfigurationProperty(displayMessageKey = "createScript.display", helpMessageKey = "createScript.help", order = 3) + public String getCreateScript() { + return createScript; + } + + public void setCreateScript(String value) { + this.createScript = value; + } + + /** + * Update script string. + */ + private String updateScript = ""; + + @ConfigurationProperty(displayMessageKey = "updateScript.display", helpMessageKey = "updateScript.help", order = 4) + public String getUpdateScript() { + return updateScript; + } + + public void setUpdateScript(String value) { + this.updateScript = value; + } + + /** + * Delete script string. + */ + private String deleteScript = ""; + + @ConfigurationProperty(displayMessageKey = "deleteScript.display", helpMessageKey = "deleteScript.help", order = 5) + public String getDeleteScript() { + return deleteScript; + } + + public void setDeleteScript(String value) { + this.deleteScript = value; + } + + /** + * Search script string. + */ + private String searchScript = ""; + + @ConfigurationProperty(displayMessageKey = "searchScript.display", helpMessageKey = "searchScript.help", order = 6) + public String getSearchScript() { + return searchScript; + } + + public void setSearchScript(String value) { + this.searchScript = value; + } + + /** + * Authenticate script string. + */ + private String authenticateScript = ""; + + @ConfigurationProperty(displayMessageKey = "authenticateScript.display", + helpMessageKey = "authenticateScript.help", order = 6) + public String getAuthenticateScript() { + return authenticateScript; + } + + public void setAuthenticateScript(String value) { + this.authenticateScript = value; + } + + /** + * Resolve username script string. + */ + private String resolveUsernameScript = ""; + + @ConfigurationProperty(displayMessageKey = "resolveUsernameScript.display", helpMessageKey = + "resolveUsernameScript.help", order = 6) + public String getResolveUsernameScript() { + return resolveUsernameScript; + } + + public void setResolveUsernameScript(String value) { + this.resolveUsernameScript = value; + } + + /** + * Sync script string. + */ + private String syncScript = ""; + + @ConfigurationProperty(displayMessageKey = "syncScript.display", helpMessageKey = "syncScript.help", order = 7) + public String getSyncScript() { + return syncScript; + } + + public void setSyncScript(String value) { + this.syncScript = value; + } + + /** + * Schema script string. + */ + private String schemaScript = ""; + + @ConfigurationProperty(displayMessageKey = "schemaScript.display", helpMessageKey = "schemaScript.help", order = 8) + public String getSchemaScript() { + return schemaScript; + } + + public void setSchemaScript(String value) { + this.schemaScript = value; + } + + /** + * Test script string. + */ + private String testScript = ""; + + @ConfigurationProperty(displayMessageKey = "testScript.display", helpMessageKey = "testScript.help", order = 9) + public String getTestScript() { + return testScript; + } + + public void setTestScript(String value) { + this.testScript = value; + } + + /** + * Create script filename. + */ + private String createScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "createScriptFileName.display", + helpMessageKey = "createScriptFileName.help", order = 10) + public String getCreateScriptFileName() { + return createScriptFileName; + } + + public void setCreateScriptFileName(String value) { + this.createScriptFileName = value; + } + + /** + * Update script FileName. + */ + private String updateScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "updateScriptFileName.display", + helpMessageKey = "updateScriptFileName.help", order = 11) + public String getUpdateScriptFileName() { + return updateScriptFileName; + } + + public void setUpdateScriptFileName(String value) { + this.updateScriptFileName = value; + } + + /** + * Delete script FileName. + */ + private String deleteScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "deleteScriptFileName.display", + helpMessageKey = "deleteScriptFileName.help", order = 12) + public String getDeleteScriptFileName() { + return deleteScriptFileName; + } + + public void setDeleteScriptFileName(String value) { + this.deleteScriptFileName = value; + } + + /** + * Search script FileName. + */ + private String searchScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "searchScriptFileName.display", + helpMessageKey = "searchScriptFileName.help", order = 13) + public String getSearchScriptFileName() { + return searchScriptFileName; + } + + public void setSearchScriptFileName(String value) { + this.searchScriptFileName = value; + } + + /** + * Authenticate script FileName. + */ + private String authenticateScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "authenticateScriptFileName.display", + helpMessageKey = "authenticateScriptFileName.help", order = 14) + public String getAuthenticateScriptFileName() { + return authenticateScriptFileName; + } + + public void setAuthenticateScriptFileName(String value) { + this.authenticateScriptFileName = value; + } + + /** + * Resolve username script FileName. + */ + private String resolveUsernameScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "resolveUsernameScriptFileName.display", + helpMessageKey = "resolveUsernameScriptFileName.help", order = 15) + public String getResolveUsernameScriptFileName() { + return resolveUsernameScriptFileName; + } + + public void setResolveUsernameScriptFileName(String value) { + this.resolveUsernameScriptFileName = value; + } + + /** + * Sync script FileName. + */ + private String syncScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "syncScriptFileName.display", + helpMessageKey = "syncScriptFileName.help", order = 16) + public String getSyncScriptFileName() { + return syncScriptFileName; + } + + public void setSyncScriptFileName(String value) { + this.syncScriptFileName = value; + } + + /** + * Schema script FileName. + */ + private String schemaScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "schemaScriptFileName.display", + helpMessageKey = "schemaScriptFileName.help", order = 17) + public String getSchemaScriptFileName() { + return schemaScriptFileName; + } + + public void setSchemaScriptFileName(String value) { + this.schemaScriptFileName = value; + } + + /** + * Test script FileName. + */ + private String testScriptFileName = null; + + @ConfigurationProperty(displayMessageKey = "testScriptFileName.display", + helpMessageKey = "testScriptFileName.help", order = 18) + public String getTestScriptFileName() { + return testScriptFileName; + } + + public void setTestScriptFileName(String value) { + this.testScriptFileName = value; + } + + // ======================================================================= + // Configuration Interface + // ======================================================================= + /** + * Attempt to validate the arguments added to the Configuration. + */ + @Override + public void validate() { + // Validate the actions + LOG.info("Checking Create Script filename"); + checkFileIsReadable("Create", getCreateScriptFileName()); + LOG.info("Checking Update Script filename"); + checkFileIsReadable("Update", getUpdateScriptFileName()); + LOG.info("Checking Delete Script filename"); + checkFileIsReadable("Delete", getDeleteScriptFileName()); + LOG.info("Checking Search Script filename"); + checkFileIsReadable("Search", getSearchScriptFileName()); + LOG.info("Checking Sync Script filename"); + checkFileIsReadable("Sync", getSyncScriptFileName()); + LOG.info("Checking Test Script filename"); + checkFileIsReadable("Test", getTestScriptFileName()); + } + + /** + * Format message with arguments. + * + * @param key key of the message + * @param objects arguments + * @return the localized message string + */ + public String getMessage(String key, Object... objects) { + final String fmt = getConnectorMessages().format(key, key, objects); + LOG.ok("Get for a key {0} connector message {1}", key, fmt); + return fmt; + } + + private void checkFileIsReadable(final String type, final String fileName) { + if (fileName == null) { + LOG.ok("{0} Script Filename is null", type); + } else { + File file = new File(AbstractScriptedConnector.resolveVariables(fileName)); + try { + if (file.canRead()) { + LOG.ok("{0} is readable", fileName); + } else { + throw new IllegalArgumentException("Can't read " + fileName); + } + } catch (SecurityException e) { + throw new IllegalArgumentException("Can't read " + fileName, e); + } + } + } + +} diff --git a/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConnector.java b/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConnector.java new file mode 100644 index 00000000..14a5cd81 --- /dev/null +++ b/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/AbstractScriptedConnector.java @@ -0,0 +1,898 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 ConnId. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +package net.tirasa.connid.commons.scripted; + +import static net.tirasa.connid.commons.scripted.Constants.MSG_OBJECT_CLASS_REQUIRED; +import static net.tirasa.connid.commons.scripted.Constants.MSG_INVALID_ATTRIBUTE_SET; +import static net.tirasa.connid.commons.scripted.Constants.MSG_BLANK_UID; +import static net.tirasa.connid.commons.scripted.Constants.MSG_BLANK_RESULT_HANDLER; +import static net.tirasa.connid.commons.scripted.Constants.MSG_INVALID_SCRIPT; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.identityconnectors.common.CollectionUtil; +import org.identityconnectors.common.IOUtil; +import org.identityconnectors.common.StringUtil; +import org.identityconnectors.common.logging.Log; +import org.identityconnectors.common.script.ScriptExecutor; +import org.identityconnectors.common.script.ScriptExecutorFactory; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.common.security.SecurityUtil; +import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp; +import org.identityconnectors.framework.common.FrameworkUtil; +import org.identityconnectors.framework.common.exceptions.ConnectorException; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeDelta; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder; +import org.identityconnectors.framework.common.objects.Name; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.common.objects.OperationalAttributes; +import org.identityconnectors.framework.common.objects.ResultsHandler; +import org.identityconnectors.framework.common.objects.Schema; +import org.identityconnectors.framework.common.objects.SchemaBuilder; +import org.identityconnectors.framework.common.objects.ScriptContext; +import org.identityconnectors.framework.common.objects.SearchResult; +import org.identityconnectors.framework.common.objects.SyncDeltaBuilder; +import org.identityconnectors.framework.common.objects.SyncDeltaType; +import org.identityconnectors.framework.common.objects.SyncResultsHandler; +import org.identityconnectors.framework.common.objects.SyncToken; +import org.identityconnectors.framework.common.objects.Uid; +import org.identityconnectors.framework.spi.Configuration; +import org.identityconnectors.framework.spi.Connector; +import org.identityconnectors.framework.spi.SearchResultsHandler; +import org.identityconnectors.framework.spi.operations.AuthenticateOp; +import org.identityconnectors.framework.spi.operations.CreateOp; +import org.identityconnectors.framework.spi.operations.DeleteOp; +import org.identityconnectors.framework.spi.operations.SchemaOp; +import org.identityconnectors.framework.spi.operations.ScriptOnConnectorOp; +import org.identityconnectors.framework.spi.operations.SearchOp; +import org.identityconnectors.framework.spi.operations.SyncOp; +import org.identityconnectors.framework.spi.operations.TestOp; +import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp; +import org.identityconnectors.framework.spi.operations.UpdateDeltaOp; +import org.identityconnectors.framework.spi.operations.UpdateOp; + +public abstract class AbstractScriptedConnector implements Connector, + CreateOp, UpdateOp, UpdateDeltaOp, UpdateAttributeValuesOp, DeleteOp, AuthenticateOp, + ResolveUsernameApiOp, SchemaOp, SyncOp, TestOp, SearchOp>, ScriptOnConnectorOp { + + protected static final Log LOG = Log.getLog(AbstractScriptedConnector.class); + + private static final Pattern VARIABLE = Pattern.compile("\\$\\{[a-zA-Z]+\\w*\\}"); + + public static final String resolveVariables(final String input) { + Set vars = new HashSet<>(); + Matcher matcher = VARIABLE.matcher(input); + while (matcher.find()) { + int n = 0; + for (int i = matcher.start() - 1; i >= 0 && input.charAt(i) == '\\'; i--) { + n++; + } + if (n % 2 != 0) { + continue; + } + + vars.add(input.substring(matcher.start() + 2, matcher.end() - 1)); + } + + String resolved = input; + for (String var : vars) { + String replacement = System.getProperty(var); + if (replacement != null) { + resolved = resolved.replace("${" + var + "}", replacement); + } + } + return resolved; + } + + protected C config; + + private Schema schema; + + private ScriptExecutorFactory factory; + + private ScriptExecutor createExecutor; + + private ScriptExecutor updateExecutor; + + private ScriptExecutor deleteExecutor; + + private ScriptExecutor searchExecutor; + + private ScriptExecutor authenticateExecutor; + + private ScriptExecutor resolveUsernameExecutor; + + private ScriptExecutor syncExecutor; + + private ScriptExecutor schemaExecutor; + + private ScriptExecutor testExecutor; + + private ScriptExecutor runOnConnectorExecutor; + + @Override + public C getConfiguration() { + return config; + } + + @Override + public void dispose() { + // nothing to do + } + + private ScriptExecutor getScriptExecutor(String script, String scriptFileName) { + String scriptCode = script; + ScriptExecutor scriptExec = null; + + try { + if (scriptFileName != null) { + scriptCode = IOUtil.readFileUTF8(new File(resolveVariables(scriptFileName))); + } + if (scriptCode.length() > 0) { + scriptExec = factory.newScriptExecutor(getClass().getClassLoader(), scriptCode, true); + } + } catch (IOException e) { + throw new ConnectorException("Script error", e); + } + return scriptExec; + } + + @Override + @SuppressWarnings("unchecked") + public void init(final Configuration cfg) { + this.config = (C) cfg; + + this.factory = ScriptExecutorFactory.newInstance(config.getScriptingLanguage()); + + // We need an executor for each and every script. At least, they'll get evaluated and compiled. + // We privilege the script file over the script string if script filename is null, then we use the script string + if (checkReloadScript(createExecutor, config.getCreateScript(), config.getCreateScriptFileName())) { + createExecutor = getScriptExecutor(config.getCreateScript(), config.getCreateScriptFileName()); + LOG.ok("Create script loaded"); + } + + if (checkReloadScript(updateExecutor, config.getUpdateScript(), config.getUpdateScriptFileName())) { + updateExecutor = getScriptExecutor(config.getUpdateScript(), config.getUpdateScriptFileName()); + LOG.ok("Update script loaded"); + } + + if (checkReloadScript(deleteExecutor, config.getDeleteScript(), config.getDeleteScriptFileName())) { + deleteExecutor = getScriptExecutor(config.getDeleteScript(), config.getDeleteScriptFileName()); + LOG.ok("Delete script loaded"); + } + + if (checkReloadScript(searchExecutor, config.getSearchScript(), config.getSearchScriptFileName())) { + searchExecutor = getScriptExecutor(config.getSearchScript(), config.getSearchScriptFileName()); + LOG.ok("Search script loaded"); + } + + if (checkReloadScript(authenticateExecutor, config.getAuthenticateScript(), config. + getAuthenticateScriptFileName())) { + authenticateExecutor = getScriptExecutor( + config.getAuthenticateScript(), config.getAuthenticateScriptFileName()); + LOG.ok("Authenticate script loaded"); + } + + if (checkReloadScript(resolveUsernameExecutor, config.getResolveUsernameScript(), config. + getResolveUsernameScriptFileName())) { + resolveUsernameExecutor = getScriptExecutor( + config.getResolveUsernameScript(), config.getResolveUsernameScriptFileName()); + LOG.ok("ResolveUsername script loaded"); + } + + if (checkReloadScript(syncExecutor, config.getSyncScript(), config.getSyncScriptFileName())) { + syncExecutor = getScriptExecutor(config.getSyncScript(), config.getSyncScriptFileName()); + LOG.ok("Sync script loaded"); + } + + if (checkReloadScript(schemaExecutor, config.getSchemaScript(), config.getSchemaScriptFileName())) { + schemaExecutor = getScriptExecutor(config.getSchemaScript(), config.getSchemaScriptFileName()); + LOG.ok("Schema script loaded"); + } + + if (checkReloadScript(testExecutor, config.getTestScript(), config.getTestScriptFileName())) { + testExecutor = getScriptExecutor(config.getTestScript(), config.getTestScriptFileName()); + LOG.ok("Test script loaded"); + } + LOG.ok("Connector {0} successfully inited", getClass().getName()); + } + + protected abstract Map buildArguments(); + + @Override + public Uid create( + final ObjectClass objectClass, + final Set createAttributes, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + createExecutor = getScriptExecutor(config.getCreateScript(), config.getCreateScriptFileName()); + LOG.ok("Create script loaded"); + } + if (createExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("Object class: {0}", objectClass.getObjectClassValue()); + + if (createAttributes == null || createAttributes.isEmpty()) { + throw new IllegalArgumentException(config.getMessage(MSG_INVALID_ATTRIBUTE_SET)); + } + + Map arguments = buildArguments(); + + arguments.put("action", "CREATE"); + arguments.put("log", LOG); + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("options", options.getOptions()); + // We give the id (name) as an argument, more friendly than dealing with __NAME__ + String id = null; + Name name = AttributeUtil.getNameFromAttributes(createAttributes); + if (name == null) { + Uid uid = AttributeUtil.getUidAttribute(createAttributes); + if (uid != null) { + id = uid.getUidValue(); + } + } else { + id = name.getNameValue(); + } + arguments.put("id", id); + + Map> attrMap = new HashMap<>(); + for (Attribute attr : createAttributes) { + attrMap.put(attr.getName(), attr.getValue()); + } + // let's get rid of __NAME__ + attrMap.remove(Name.NAME); + arguments.put("attributes", attrMap); + + // Password - if allowed we provide it in clear + if (config.getClearTextPasswordToScript()) { + GuardedString gpasswd = AttributeUtil.getPasswordValue(createAttributes); + arguments.put("password", gpasswd == null ? null : SecurityUtil.decrypt(gpasswd)); + } + + try { + Object uidAfter = createExecutor.execute(arguments); + if (uidAfter instanceof String) { + LOG.ok("{0} created", uidAfter); + return new Uid((String) uidAfter); + } else { + throw new ConnectorException("Create script didn't return with the __UID__ value"); + } + } catch (Exception e) { + throw new ConnectorException("Create script error", e); + } + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + private Map attributes2Arguments(final boolean plainUpdate, final Set attrs) { + if (CollectionUtil.isEmpty(attrs)) { + throw new IllegalArgumentException(config.getMessage(MSG_INVALID_ATTRIBUTE_SET)); + } + + Map> attrMap = new HashMap<>(); + for (Attribute attr : attrs) { + if (OperationalAttributes.isOperationalAttribute(attr)) { + if (plainUpdate) { + attrMap.put(attr.getName(), attr.getValue()); + } + } else { + attrMap.put(attr.getName(), attr.getValue()); + } + } + + Map arguments = new HashMap<>(); + arguments.put("attributes", attrMap); + + // Do we need to update the password? + if (config.getClearTextPasswordToScript() && plainUpdate) { + GuardedString gpasswd = AttributeUtil.getPasswordValue(attrs); + arguments.put("password", gpasswd == null ? null : SecurityUtil.decrypt(gpasswd)); + } + + return arguments; + } + + private Uid genericUpdate( + final String method, + final ObjectClass objectClass, + final Uid uid, + final Map additionalArguments, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + updateExecutor = getScriptExecutor(config.getUpdateScript(), config.getUpdateScriptFileName()); + LOG.ok("Update ({0}) script loaded", method); + } + if (updateExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("Object class: {0}", objectClass.getObjectClassValue()); + + if (uid == null || uid.getUidValue() == null) { + throw new IllegalArgumentException(config.getMessage(MSG_BLANK_UID)); + } + String id = uid.getUidValue(); + + Map arguments = buildArguments(); + arguments.putAll(additionalArguments); + + arguments.put("action", method); + arguments.put("log", LOG); + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("uid", id); + arguments.put("options", options.getOptions()); + + try { + Object uidAfter = updateExecutor.execute(arguments); + if (uidAfter instanceof String) { + LOG.ok("{0} updated ({1})", uidAfter, method); + return new Uid((String) uidAfter); + } + } catch (Exception e) { + throw new ConnectorException("Update(" + method + ") script error", e); + } + throw new ConnectorException("Update script didn't return with the __UID__ value"); + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public Uid update( + final ObjectClass objectClass, + final Uid uid, + final Set replaceAttributes, + final OperationOptions options) { + + return genericUpdate("UPDATE", objectClass, uid, attributes2Arguments(true, replaceAttributes), options); + } + + @Override + public Set updateDelta( + final ObjectClass objectClass, + final Uid uid, + final Set modifications, + final OperationOptions options) { + + if (CollectionUtil.isEmpty(modifications)) { + throw new IllegalArgumentException(config.getMessage(MSG_INVALID_ATTRIBUTE_SET)); + } + + Map> valuesToAdd = new HashMap<>(); + Map> valuesToRemove = new HashMap<>(); + Map> valuesToReplace = new HashMap<>(); + for (AttributeDelta attr : modifications) { + valuesToAdd.put(attr.getName(), attr.getValuesToAdd()); + valuesToRemove.put(attr.getName(), attr.getValuesToRemove()); + valuesToReplace.put(attr.getName(), attr.getValuesToReplace()); + } + + Map arguments = new HashMap<>(); + arguments.put("valuesToAdd", valuesToAdd); + arguments.put("valuesToRemove", valuesToRemove); + arguments.put("valuesToReplace", valuesToReplace); + + genericUpdate("UPDATE_DELTA", objectClass, uid, arguments, options); + return modifications; + } + + @Override + public Uid addAttributeValues( + final ObjectClass objectClass, + final Uid uid, + final Set valuesToAdd, + final OperationOptions options) { + + return genericUpdate("ADD_ATTRIBUTE_VALUES", + objectClass, uid, attributes2Arguments(false, valuesToAdd), options); + } + + @Override + public Uid removeAttributeValues( + final ObjectClass objectClass, + final Uid uid, Set valuesToRemove, + final OperationOptions options) { + + return genericUpdate("REMOVE_ATTRIBUTE_VALUES", + objectClass, uid, attributes2Arguments(false, valuesToRemove), options); + } + + @Override + public void delete( + final ObjectClass objectClass, + final Uid uid, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + deleteExecutor = getScriptExecutor(config.getDeleteScript(), config.getDeleteScriptFileName()); + LOG.ok("Delete script loaded"); + } + if (deleteExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("Object class: {0}", objectClass.getObjectClassValue()); + + if (uid == null || uid.getUidValue() == null) { + throw new IllegalArgumentException(config.getMessage(MSG_BLANK_UID)); + } + String id = uid.getUidValue(); + + Map arguments = buildArguments(); + + arguments.put("action", "DELETE"); + arguments.put("log", LOG); + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("uid", id); + arguments.put("options", options.getOptions()); + + try { + deleteExecutor.execute(arguments); + LOG.ok("{0} deleted", id); + } catch (Exception e) { + throw new ConnectorException("Delete script error", e); + } + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public Uid authenticate( + final ObjectClass objectClass, + final String username, + final GuardedString password, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + authenticateExecutor = getScriptExecutor( + config.getAuthenticateScript(), config.getAuthenticateScriptFileName()); + LOG.ok("Authenticate script loaded"); + } + if (authenticateExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("Object class: {0}", objectClass.getObjectClassValue()); + + Map arguments = buildArguments(); + + arguments.put("action", "AUTHENTICATE"); + arguments.put("log", LOG); + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("username", username); + arguments.put("password", + config.getClearTextPasswordToScript() ? SecurityUtil.decrypt(password) : password); + arguments.put("options", options.getOptions()); + + try { + Object uid = authenticateExecutor.execute(arguments); + if (uid instanceof String) { + LOG.ok("{0} authenticated", uid); + return new Uid((String) uid); + } + } catch (Exception e) { + throw new ConnectorException("Authenticate script error", e); + } + throw new ConnectorException("Authenticate script didn't return with the __UID__ value"); + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public Uid resolveUsername( + final ObjectClass objectClass, + final String username, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + resolveUsernameExecutor = getScriptExecutor( + config.getResolveUsernameScript(), config.getResolveUsernameScriptFileName()); + LOG.ok("ResolveUsername script loaded"); + } + if (resolveUsernameExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("Object class: {0}", objectClass.getObjectClassValue()); + + Map arguments = buildArguments(); + + arguments.put("action", "RESOLVE USERNAME"); + arguments.put("log", LOG); + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("username", username); + arguments.put("options", options.getOptions()); + + try { + Object uid = resolveUsernameExecutor.execute(arguments); + if (uid instanceof String) { + LOG.ok("{0} resolved", uid); + return new Uid((String) uid); + } + } catch (Exception e) { + throw new ConnectorException("ResolveUsername script error", e); + } + throw new ConnectorException("ResolveUsername script didn't return with the __UID__ value"); + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public Schema schema() { + SchemaBuilder scmb = new SchemaBuilder(getClass()); + if (config.isReloadScriptOnExecution()) { + schemaExecutor = getScriptExecutor("", config.getSchemaScriptFileName()); + } + if (schemaExecutor != null) { + Map arguments = buildArguments(); + + arguments.put("action", "SCHEMA"); + arguments.put("log", LOG); + arguments.put("builder", scmb); + try { + schemaExecutor.execute(arguments); + } catch (Exception e) { + throw new ConnectorException("Schema script error", e); + } + } else { + throw new UnsupportedOperationException("SCHEMA script executor is null. Problem loading Schema script"); + } + schema = scmb.build(); + return schema; + } + + @Override + public void executeQuery( + final ObjectClass objectClass, + final Map query, + final ResultsHandler handler, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + searchExecutor = getScriptExecutor(config.getSearchScript(), config.getSearchScriptFileName()); + LOG.ok("Search script loaded"); + } + if (searchExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("ObjectClass: {0}", objectClass.getObjectClassValue()); + if (handler == null) { + throw new IllegalArgumentException(config.getMessage(MSG_BLANK_RESULT_HANDLER)); + } + + Map arguments = buildArguments(); + + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("action", "SEARCH"); + arguments.put("log", LOG); + arguments.put("options", options.getOptions()); + arguments.put("query", query); + + try { + @SuppressWarnings("unchecked") + List> results = (List>) searchExecutor.execute(arguments); + LOG.ok("Search ok"); + processResults(objectClass, results, handler); + } catch (Exception e) { + throw new ConnectorException("Search script error", e); + } + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public void sync( + final ObjectClass objectClass, + final SyncToken token, + final SyncResultsHandler handler, + final OperationOptions options) { + + if (config.isReloadScriptOnExecution()) { + syncExecutor = getScriptExecutor(config.getSyncScript(), config.getSyncScriptFileName()); + LOG.ok("Sync script loaded"); + } + if (syncExecutor != null) { + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("ObjectClass: {0}", objectClass.getObjectClassValue()); + if (handler == null) { + throw new IllegalArgumentException(config.getMessage(MSG_BLANK_RESULT_HANDLER)); + } + + Map arguments = buildArguments(); + + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("action", "SYNC"); + arguments.put("log", LOG); + arguments.put("options", options.getOptions()); + arguments.put("token", token == null ? null : token.getValue()); + + try { + @SuppressWarnings("unchecked") + List> results = (List>) syncExecutor.execute(arguments); + LOG.ok("Sync ok"); + processDeltas(objectClass, results, handler); + } catch (Exception e) { + throw new ConnectorException("Sync script error", e); + } + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public SyncToken getLatestSyncToken(final ObjectClass objectClass) { + if (config.isReloadScriptOnExecution()) { + syncExecutor = getScriptExecutor(config.getSyncScript(), config.getSyncScriptFileName()); + LOG.ok("Sync script loaded"); + } + if (syncExecutor != null) { + SyncToken st = null; + if (objectClass == null) { + throw new IllegalArgumentException(config.getMessage(MSG_OBJECT_CLASS_REQUIRED)); + } + LOG.ok("ObjectClass: {0}", objectClass.getObjectClassValue()); + + Map arguments = buildArguments(); + + arguments.put("objectClass", objectClass.getObjectClassValue()); + arguments.put("action", "GET_LATEST_SYNC_TOKEN"); + arguments.put("log", LOG); + + try { + // We expect the script to return a value (or null) that makes the sync token + // !! result has to be one of the framework known types... + Object result = syncExecutor.execute(arguments); + LOG.ok("GetLatestSyncToken ok"); + FrameworkUtil.checkAttributeType(result.getClass()); + st = new SyncToken(result); + } catch (java.lang.IllegalArgumentException ae) { + throw new ConnectorException("Unknown Token type", ae); + } catch (Exception e) { + throw new ConnectorException("Sync (GetLatestSyncToken) script error", e); + } + return st; + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public Object runScriptOnConnector(final ScriptContext request, final OperationOptions options) { + Object result = null; + try { + if (request.getScriptText() != null && request.getScriptText().length() > 0) { + assert request.getScriptLanguage().equalsIgnoreCase(config.getScriptingLanguage()); + runOnConnectorExecutor = factory.newScriptExecutor( + getClass().getClassLoader(), request.getScriptText(), true); + } + } catch (Exception e) { + throw new ConnectorException("RunOnConnector script parse error", e); + } + if (runOnConnectorExecutor != null) { + Map arguments = buildArguments(); + + arguments.put("action", "RUNSCRIPTONCONNECTOR"); + arguments.put("log", LOG); + arguments.put("options", options.getOptions()); + arguments.put("scriptsArguments", request.getScriptArguments()); + + try { + // We return any object from the script + result = runOnConnectorExecutor.execute(arguments); + LOG.ok("runOnConnector script ok"); + } catch (Exception e) { + throw new ConnectorException("runOnConnector script error", e); + } + return result; + } else { + throw new UnsupportedOperationException(config.getMessage(MSG_INVALID_SCRIPT)); + } + } + + @Override + public void test() { + config.validate(); + + if (config.isReloadScriptOnExecution()) { + testExecutor = getScriptExecutor(config.getTestScript(), config.getTestScriptFileName()); + LOG.ok("Test script loaded"); + } + if (testExecutor != null) { + Map arguments = buildArguments(); + arguments.put("action", "TEST"); + arguments.put("log", LOG); + + try { + testExecutor.execute(arguments); + LOG.ok("Test ok"); + } catch (Exception e) { + throw new ConnectorException("Test script error", e); + } + } + } + + private void processResults( + final ObjectClass objectClass, + final List> results, + final ResultsHandler handler) { + + String pagedResultCookie = null; + for (Map result : results) { + // special handling of paged result cookie + if (result.size() == 1) { + Map.Entry entry = result.entrySet().iterator().next(); + if (OperationOptions.OP_PAGED_RESULTS_COOKIE.equalsIgnoreCase(entry.getKey()) + && entry.getValue() != null) { + + pagedResultCookie = entry.getValue().toString(); + } + } else { + ConnectorObjectBuilder cobld = new ConnectorObjectBuilder(); + for (Map.Entry entry : result.entrySet()) { + final String attrName = entry.getKey(); + final Object attrValue = entry.getValue(); + // Special first + if (Uid.NAME.equalsIgnoreCase(attrName)) { + if (attrValue == null) { + throw new IllegalArgumentException("Uid cannot be null"); + } + cobld.setUid(attrValue.toString()); + } else if (Name.NAME.equalsIgnoreCase(attrName)) { + if (attrValue == null) { + throw new IllegalArgumentException("Name cannot be null"); + } + cobld.setName(attrValue.toString()); + } else if (attrName.equalsIgnoreCase("password")) { + // is there a chance we fetch password from search? + } else if (attrValue instanceof Collection) { + cobld.addAttribute(AttributeBuilder.build(attrName, (Collection) attrValue)); + } else if (attrValue != null) { + cobld.addAttribute(AttributeBuilder.build(attrName, attrValue)); + } else { + cobld.addAttribute(AttributeBuilder.build(attrName)); + } + } + cobld.setObjectClass(objectClass); + handler.handle(cobld.build()); + LOG.ok("ConnectorObject is built"); + } + } + + if (handler instanceof SearchResultsHandler) { + SearchResultsHandler.class.cast(handler).handleResult(new SearchResult(pagedResultCookie, -1)); + } else { + LOG.warn("Not expected, but found {0}: {1}", + OperationOptions.OP_PAGED_RESULTS_COOKIE, pagedResultCookie); + } + } + + @SuppressWarnings("unchecked") + private void processDeltas( + final ObjectClass objectClass, + final List> results, + final SyncResultsHandler handler) { + + for (Map result : results) { + // The Map should look like: + // token: token + // operation: CREATE_OR_UPDATE|DELETE (defaults to CREATE_OR_UPDATE) + // uid: uid + // previousUid: prevuid (This is for rename ops) + // password: password + // attributes: of attributes name/values + SyncDeltaBuilder syncbld = new SyncDeltaBuilder(); + String uid = (String) result.get("uid"); + if (uid != null && !uid.isEmpty()) { + syncbld.setUid(new Uid(uid)); + Object token = result.get("token"); + // Null token, set some acceptable value + if (token == null) { + LOG.ok("token value is null, replacing to 0L"); + token = 0L; + } + syncbld.setToken(new SyncToken(token)); + + // Start building the connector object + ConnectorObjectBuilder cobld = new ConnectorObjectBuilder(); + cobld.setName(uid); + cobld.setUid(uid); + cobld.setObjectClass(objectClass); + + // operation + String op = (String) result.get("operation"); + // if operation is null we assume this is CREATE_OR_UPDATE + syncbld.setDeltaType(op != null && SyncDeltaType.DELETE.name().equalsIgnoreCase(op) + ? SyncDeltaType.DELETE + : SyncDeltaType.CREATE_OR_UPDATE); + // previous UID + String prevUid = (String) result.get("previousUid"); + if (prevUid != null && !prevUid.isEmpty()) { + syncbld.setPreviousUid(new Uid(prevUid)); + } + + // password? is password valid if empty string? let's assume yes... + if (result.get("password") != null) { + cobld.addAttribute( + AttributeBuilder.buildCurrentPassword(((String) result.get("password")).toCharArray())); + } + + // Remaining attributes + Object attributes = result.get("attributes"); + if (attributes != null) { + for (Map.Entry attr : ((Map) attributes).entrySet()) { + final String attrName = attr.getKey(); + final Object attrValue = attr.getValue(); + if (attrValue instanceof Collection) { + cobld.addAttribute(AttributeBuilder.build(attrName, (Collection) attrValue)); + } else if (attrValue != null) { + cobld.addAttribute(AttributeBuilder.build(attrName, attrValue)); + } else { + cobld.addAttribute(AttributeBuilder.build(attrName)); + } + } + } + + syncbld.setObject(cobld.build()); + if (!handler.handle(syncbld.build())) { + LOG.ok("Stop processing of the sync result set"); + break; + } + } else { + // we have a null uid... mmmm.... + } + } + } + + private boolean checkReloadScript( + final ScriptExecutor scriptExecutor, + final String scriptString, + final String scriptFileName) { + + return (StringUtil.isNotBlank(scriptFileName) || StringUtil.isNotBlank(scriptString)) + && (scriptExecutor == null || config.isReloadScriptOnExecution()); + } +} diff --git a/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/Constants.java b/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/Constants.java new file mode 100644 index 00000000..6a67a3ed --- /dev/null +++ b/java/commons/scripted/src/main/java/net/tirasa/connid/commons/scripted/Constants.java @@ -0,0 +1,40 @@ +/* + * ==================== + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 ConnId. All rights reserved. + * + * The contents of this file are subject to the terms of the Common Development + * and Distribution License("CDDL") (the "License"). You may not use this file + * except in compliance with the License. + * + * You can obtain a copy of the License at + * http://opensource.org/licenses/cddl1.php + * See the License for the specific language governing permissions and limitations + * under the License. + * + * When distributing the Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://opensource.org/licenses/cddl1.php. + * If applicable, add the following below this CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * ==================== + */ +package net.tirasa.connid.commons.scripted; + +public final class Constants { + + public static final String MSG_OBJECT_CLASS_REQUIRED = "objectClass.required"; + + public static final String MSG_INVALID_SCRIPT = "script.invalid"; + + public static final String MSG_INVALID_ATTRIBUTE_SET = "invalid.attribute.set"; + + public static final String MSG_BLANK_UID = "blank.uid"; + + public static final String MSG_BLANK_RESULT_HANDLER = "blank.result.hanlder"; + + private Constants() { + // private constructor for static utility class + } +} diff --git a/java/commons/scripted/src/main/resources/net/tirasa/connid/commons/scripted/Messages.properties b/java/commons/scripted/src/main/resources/net/tirasa/connid/commons/scripted/Messages.properties new file mode 100644 index 00000000..dd5b7055 --- /dev/null +++ b/java/commons/scripted/src/main/resources/net/tirasa/connid/commons/scripted/Messages.properties @@ -0,0 +1,28 @@ +# +# ==================== +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2016 ConnId. All rights reserved. +# +# The contents of this file are subject to the terms of the Common Development +# and Distribution License("CDDL") (the "License"). You may not use this file +# except in compliance with the License. +# +# You can obtain a copy of the License at +# http://opensource.org/licenses/cddl1.php +# See the License for the specific language governing permissions and limitations +# under the License. +# +# When distributing the Covered Code, include this CDDL Header Notice in each file +# and include the License file at http://opensource.org/licenses/cddl1.php. +# If applicable, add the following below this CDDL Header, with the fields +# enclosed by brackets [] replaced by your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# ==================== +# + +objectClass.required=Operation requires an 'ObjectClass'. +invalid.attribute.set=Invalid attributes set. +blank.uid=Operation requires a valid Uid! +blank.result.hanlder=Result handler is null! +script.invalid=Unable to load script diff --git a/java/pom.xml b/java/pom.xml index 30061b31..afd2898f 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -523,6 +523,7 @@ connector-framework-osgi connector-framework-contract slf4j-logging + commons connector-server-zip archetype