/* * ==================== * 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://IdentityConnectors.dev.java.net/legal/license.txt * 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 identityconnectors/legal/license.txt. * 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 org.identityconnectors.databasetable; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.AfterClass; import org.testng.annotations.Test; import org.testng.annotations.BeforeClass; import static org.identityconnectors.common.ByteUtil.randomBytes; import static org.identityconnectors.common.StringUtil.randomString; import java.io.File; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.spi.InitialContextFactory; import javax.sql.DataSource; import org.identityconnectors.common.CollectionUtil; import org.identityconnectors.common.IOUtil; import org.identityconnectors.common.StringUtil; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.databasetable.mapping.MappingStrategy; import org.identityconnectors.dbcommon.ExpectProxy; import org.identityconnectors.dbcommon.SQLParam; import org.identityconnectors.dbcommon.SQLUtil; 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.AttributeInfo; import org.identityconnectors.framework.common.objects.AttributeUtil; import org.identityconnectors.framework.common.objects.Name; import org.identityconnectors.framework.common.objects.ObjectClass; import org.identityconnectors.framework.common.objects.ObjectClassInfo; import org.identityconnectors.framework.common.objects.Schema; import org.identityconnectors.framework.common.objects.SyncToken; import org.identityconnectors.framework.common.objects.Uid; import org.identityconnectors.test.common.TestHelpers; /** * Attempts to test the Connector with the framework. */ public class DatabaseTableDSDerbyTests extends DatabaseTableTestBase { /** * */ static final String CREATE_RES = "derbyTest.sql"; /** * */ static final String DB_DIR = "test_db2"; /** * Derby's embedded driver. */ static final String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver"; /** * Derby's embedded ds. */ static final String TEST_DS="testDS"; /** * URL used to connect to a derby database. */ static final String URL_CONN = "jdbc:derby:"+getDBDirectory().toString(); /** * URL used to connect to a derby database. */ static final String URL_CREATE = URL_CONN+";create=true"; /** * URL used to shutdown a derby database. */ static final String URL_SHUTDOWN = URL_CONN+";shutdown=true"; //The tested table static final String DB_TABLE = "Accounts"; //jndi for datasource static final String[] jndiProperties = new String[]{"java.naming.factory.initial=" + MockContextFactory.class.getName()}; // Setup/Teardown /** * Creates a temporary database based on a SQL resource file. * @throws Exception */ @BeforeClass public static void createDatabase() throws Exception { final File f = getDBDirectory(); // clear out the test database directory.. IOUtil.delete(f); // attempt to create the database in the directory.. Connection conn = null; Statement stmt = null; try { Class.forName(DRIVER); conn = DriverManager.getConnection(URL_CREATE, "", ""); // create the database.. stmt = conn.createStatement(); final String create = getResourceAsString("derbyTest.sql"); assertNotNull(create); stmt.execute(create); conn.commit(); } finally { SQLUtil.closeQuietly(stmt); SQLUtil.closeQuietly(conn); } } /** * test method */ @AfterClass public static void deleteDatabase() { try { DriverManager.getConnection(URL_SHUTDOWN); } catch (Exception ex) { //expected } } /** * Create the test configuration * @return the initialized configuration */ @Override protected DatabaseTableConfiguration getConfiguration() throws Exception { DatabaseTableConfiguration config = new DatabaseTableConfiguration(); config.setJdbcDriver(DRIVER); config.setDatasource(TEST_DS); config.setTable(DB_TABLE); config.setJndiProperties(jndiProperties); config.setChangeLogColumn(CHANGELOG); config.setKeyColumn(ACCOUNTID); config.setPasswordColumn(PASSWORD); config.setConnectorMessages(TestHelpers.createDummyMessages()); return config; } /* (non-Javadoc) * @see org.identityconnectors.databasetable.DatabaseTableTestBase#getCreateAttributeSet() */ @Override protected Set<Attribute> getCreateAttributeSet(DatabaseTableConfiguration cfg) throws Exception { Set<Attribute> ret = new HashSet<Attribute>(); ret.add(AttributeBuilder.build(Name.NAME, randomString(r, 50))); if (StringUtil.isNotBlank(cfg.getPasswordColumn())) { ret.add(AttributeBuilder.buildPassword(new GuardedString(randomString(r, 50).toCharArray()))); } else { ret.add(AttributeBuilder.build(PASSWORD, randomString(r, 40))); } ret.add(AttributeBuilder.build(MANAGER, randomString(r, 15))); ret.add(AttributeBuilder.build(MIDDLENAME, randomString(r, 50))); ret.add(AttributeBuilder.build(FIRSTNAME, randomString(r, 50))); ret.add(AttributeBuilder.build(LASTNAME, randomString(r, 50))); ret.add(AttributeBuilder.build(EMAIL, randomString(r, 50))); ret.add(AttributeBuilder.build(DEPARTMENT, randomString(r, 50))); ret.add(AttributeBuilder.build(TITLE, randomString(r, 50))); if(!cfg.getChangeLogColumn().equalsIgnoreCase(AGE)){ ret.add(AttributeBuilder.build(AGE, r.nextInt(100))); } if(!cfg.getChangeLogColumn().equalsIgnoreCase(ACCESSED)){ ret.add(AttributeBuilder.build(ACCESSED, r.nextLong())); } ret.add(AttributeBuilder.build(SALARY, new BigDecimal("360536.75"))); ret.add(AttributeBuilder.build(JPEGPHOTO, randomBytes(r, 2000))); ret.add(AttributeBuilder.build(OPENTIME, new java.sql.Time(System.currentTimeMillis()).toString())); ret.add(AttributeBuilder.build(ACTIVATE, new java.sql.Date(System.currentTimeMillis()).toString())); ret.add(AttributeBuilder.build(ENROLLED, new Timestamp(System.currentTimeMillis()).toString())); ret.add(AttributeBuilder.build(CHANGED, new Timestamp(System.currentTimeMillis()).toString())); if(!cfg.getChangeLogColumn().equalsIgnoreCase(CHANGELOG)){ ret.add(AttributeBuilder.build(CHANGELOG, new Timestamp(System.currentTimeMillis()).getTime())); } return ret; } /* (non-Javadoc) * @see org.identityconnectors.databasetable.DatabaseTableTestBase#getModifyAttributeSet() */ @Override protected Set<Attribute> getModifyAttributeSet(DatabaseTableConfiguration cfg) throws Exception { return getCreateAttributeSet(cfg); } /** * Make sure the Create call works.. * @throws Exception */ @Test public void testCreateClosedConnection() throws Exception { log.ok("testCreateClosedConnection"); DatabaseTableConfiguration cfg = getConfiguration(); DatabaseTableConnector con = getConnector(cfg); Set<Attribute> expected = getCreateAttributeSet(cfg); con.create(ObjectClass.ACCOUNT, expected, null); // attempt to get the record back.. assertEquals("Connection should be closed", true, con.getConn().getConnection().isClosed()); } /** * For testing purposes we creating connection an not the framework. * @throws Exception */ @Test public void testNoZeroSQLExceptions() throws Exception { DatabaseTableConfiguration cfg = getConfiguration(); cfg.setRethrowAllSQLExceptions(false); con = getConnector(cfg); final ExpectProxy<MappingStrategy> smse = new ExpectProxy<MappingStrategy>(); MappingStrategy sms = smse.getProxy(MappingStrategy.class); //Schema for (int i = 0; i < 15; i++) { smse.expectAndReturn("getSQLAttributeType", String.class); } //Create fail smse.expectAndThrow("setSQLParam", new SQLException("test reason", "0", 0)); //Update fail smse.expectAndThrow("setSQLParam", new SQLException("test reason", "0", 0)); con.getConn().setSms(sms); Set<Attribute> expected = getCreateAttributeSet(cfg); Uid uid = con.create(ObjectClass.ACCOUNT, expected, null); con.update(ObjectClass.ACCOUNT, uid, expected, null); assertTrue("setSQLParam not called", smse.isDone()); } /** * For testing purposes we creating connection an not the framework. * @throws Exception */ @Test(expectedExceptions = ConnectorException.class) public void testNonZeroSQLExceptions() throws Exception { DatabaseTableConfiguration cfg = getConfiguration(); cfg.setRethrowAllSQLExceptions(false); con = getConnector(cfg); final ExpectProxy<MappingStrategy> smse = new ExpectProxy<MappingStrategy>(); MappingStrategy sms = smse.getProxy(MappingStrategy.class); for (int i = 0; i < 15; i++) { smse.expectAndReturn("getSQLAttributeType", String.class); } smse.expectAndThrow("setSQLParam", new SQLException("test reason", "411", 411)); con.getConn().setSms(sms); Set<Attribute> expected = getCreateAttributeSet(cfg); con.create(ObjectClass.ACCOUNT, expected, null); assertTrue("setSQLParam not called", smse.isDone()); } /** * For testing purposes we creating connection an not the framework. * @throws Exception */ @Test(expectedExceptions = ConnectorException.class) public void testRethrowAllSQLExceptions() throws Exception { DatabaseTableConfiguration cfg = getConfiguration(); cfg.setRethrowAllSQLExceptions(true); con = getConnector(cfg); final ExpectProxy<MappingStrategy> smse = new ExpectProxy<MappingStrategy>(); MappingStrategy sms = smse.getProxy(MappingStrategy.class); for (int i = 0; i < 15; i++) { smse.expectAndReturn("getSQLAttributeType", String.class); } smse.expectAndThrow("setSQLParam", new SQLException("test reason", "0", 0)); con.getConn().setSms(sms); Set<Attribute> expected = getCreateAttributeSet(cfg); con.create(ObjectClass.ACCOUNT, expected, null); assertTrue("setSQLParam not called", smse.isDone()); } /** * For testing purposes we creating connection an not the framework. * @throws Exception */ @Test public void testSchema() throws Exception { DatabaseTableConfiguration cfg = getConfiguration(); con = getConnector(cfg); // check if this works.. Schema schema = con.schema(); checkSchema(schema); } /** * check validity of the schema * * @param schema * the schema to be checked * @throws Exception */ void checkSchema(Schema schema) throws Exception { // Schema should not be null assertNotNull(schema); Set<ObjectClassInfo> objectInfos = schema.getObjectClassInfo(); assertNotNull(objectInfos); assertEquals(1, objectInfos.size()); // get the fields from the test account final Set<Attribute> attributeSet = getCreateAttributeSet(getConfiguration()); final Map<String,Attribute> expected = AttributeUtil.toMap(attributeSet); final Set<String> keys = CollectionUtil.newCaseInsensitiveSet(); keys.addAll(expected.keySet()); // iterate through ObjectClassInfo Set for (ObjectClassInfo objectInfo : objectInfos) { assertNotNull(objectInfo); // the object class has to ACCOUNT_NAME assertTrue(objectInfo.is(ObjectClass.ACCOUNT_NAME)); // iterate through AttributeInfo Set for (AttributeInfo attInfo : objectInfo.getAttributeInfo()) { assertNotNull(attInfo); String fieldName = attInfo.getName(); if(fieldName.equalsIgnoreCase(CHANGELOG)){ keys.remove(fieldName); continue; } assertTrue("Field:" + fieldName + " doesn't exist", keys.contains(fieldName)); keys.remove(fieldName); Attribute fa = expected.get(fieldName); assertNotNull("Field:" + fieldName + " was duplicated", fa); Object field = AttributeUtil.getSingleValue(fa); Class<?> valueClass = field.getClass(); assertEquals("field: " + fieldName, valueClass, attInfo.getType()); } // all the attribute has to be removed assertEquals("There are missing attributes which were not included in the schema:"+keys, 0, keys.size()); } } /** * Test creating of the connector object, searching using UID and delete * @throws Exception * @throws SQLException */ @Test public void testGetLatestSyncToken() throws Exception { final String SQL_TEMPLATE = "UPDATE Accounts SET changelog = ? WHERE accountId = ?"; final DatabaseTableConfiguration cfg = getConfiguration(); con = getConnector(cfg); deleteAllFromAccounts(con.getConn()); final Set<Attribute> expected = getCreateAttributeSet(cfg); final Uid uid = con.create(ObjectClass.ACCOUNT, expected, null); assertNotNull(uid); final Long changelog = 9999999999999L; //Some really big value // update the last change PreparedStatement ps = null; DatabaseTableConnection conn = null; try { conn = DatabaseTableConnection.createDBTableConnection(getConfiguration()); List<SQLParam> values = new ArrayList<SQLParam>(); values.add(new SQLParam("changelog", changelog, Types.INTEGER)); values.add(new SQLParam("accountId", uid.getUidValue(), Types.VARCHAR)); ps = conn.prepareStatement(SQL_TEMPLATE, values); ps.execute(); conn.commit(); } finally { SQLUtil.closeQuietly(ps); SQLUtil.closeQuietly(conn); } // attempt to find the newly created object.. final SyncToken latestSyncToken = con.getLatestSyncToken(ObjectClass.ACCOUNT); assertNotNull(latestSyncToken); final Object actual = latestSyncToken.getValue(); assertEquals(changelog, actual); } static String getResourceAsString(String res) { return IOUtil.getResourceAsString(DatabaseTableDSDerbyTests.class, res); } /** * Context is set in jndiProperties */ public static class MockContextFactory implements InitialContextFactory { @SuppressWarnings("unchecked") public Context getInitialContext(Hashtable environment) throws NamingException { Context context = (Context) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Context.class }, new ContextIH()); return context; } } /** * MockContextFactory create the ContextIH * The looup method will return DataSourceIH */ static class ContextIH implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("lookup")) { return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { DataSource.class }, new DataSourceIH()); } return null; } } /** * ContextIH create DataSourceIH * The getConnection method will return ConnectionIH */ static class DataSourceIH implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getConnection")) { return DriverManager.getConnection(URL_CONN, "", ""); } throw new IllegalArgumentException("DataSource, invalid method:"+method.getName()); } } /** * The dir acces method * @return the file see {@link File} */ static File getDBDirectory() { return new File(System.getProperty("java.io.tmpdir"), DB_DIR); } }