package tap.config; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static tap.config.TAPConfiguration.KEY_BACKUP_BY_USER; import static tap.config.TAPConfiguration.KEY_BACKUP_FREQUENCY; import static tap.config.TAPConfiguration.KEY_DATABASE_ACCESS; import static tap.config.TAPConfiguration.KEY_DATASOURCE_JNDI_NAME; import static tap.config.TAPConfiguration.KEY_DB_PASSWORD; import static tap.config.TAPConfiguration.KEY_DB_USERNAME; import static tap.config.TAPConfiguration.KEY_JDBC_DRIVER; import static tap.config.TAPConfiguration.KEY_JDBC_URL; import static tap.config.TAPConfiguration.KEY_SQL_TRANSLATOR; import static tap.config.TAPConfiguration.VALUE_JDBC; import static tap.config.TAPConfiguration.VALUE_JNDI; import static tap.config.TAPConfiguration.VALUE_PGSPHERE; import static tap.config.TAPConfiguration.VALUE_POSTGRESQL; import java.io.File; import java.sql.SQLException; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.http.HttpServletRequest; import org.h2.jdbc.JdbcSQLException; import org.h2.jdbcx.JdbcDataSource; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import adql.db.FunctionDef; import tap.ServiceConnection; import tap.TAPException; import tap.TAPFactory; import tap.backup.DefaultTAPBackupManager; import tap.db.DBConnection; import tap.db.DBException; import tap.db.JDBCConnection; import tap.db_testtools.DBTools; import tap.formatter.OutputFormat; import tap.log.DefaultTAPLog; import tap.log.TAPLog; import tap.metadata.TAPMetadata; import uws.UWSException; import uws.job.user.JobOwner; import uws.service.UWSService; import uws.service.UWSUrl; import uws.service.UserIdentifier; import uws.service.file.LocalUWSFileManager; import uws.service.file.UWSFileManager; public class TestConfigurableTAPFactory { private static Properties validJDBCProp, validJNDIProp, incorrectDBAccessProp, missingDBAccessProp, missingDatasourceJNDINameProp, wrongDatasourceJNDINameProp, noJdbcProp1, noJdbcProp2, noJdbcProp3, badJdbcProp, missingTranslatorProp, badTranslatorProp, badDBNameProp, badUsernameProp, badPasswordProp, validBackupFrequency, noBackup, userBackup, badBackupFrequency; private static ServiceConnection serviceConnection = null; private static void setJNDIDatasource() throws NamingException{ // Create an initial JNDI context: /* note: this requires that the simple-jndi jar is in the classpath. (https://code.google.com/p/osjava/downloads/detail?name=simple-jndi-0.11.4.1.zip&can=2&q=) */ System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.memory.MemoryContextFactory"); System.setProperty("org.osjava.sj.jndi.shared", "true"); // memory shared between all instances of InitialContext // Context initialization: InitialContext ic = new InitialContext(); // Creation of a reference on a DataSource: JdbcDataSource datasource = new JdbcDataSource(); datasource.setUrl(DBTools.DB_TEST_URL); datasource.setUser(DBTools.DB_TEST_USER); datasource.setPassword(DBTools.DB_TEST_PWD); // Link the datasource with the context: ic.rebind("jdbc/MyDataSource", datasource); } @BeforeClass public static void beforeClass() throws Exception{ // BUILD A FAKE SERVICE CONNECTION: serviceConnection = new ServiceConnectionTest(); // BUILD THE DATABASE: DBTools.createTestDB(); // LOAD ALL PROPERTIES FILES NEEDED FOR ALL THE TESTS: validJDBCProp = AllTAPConfigTests.getValidProperties(); setJNDIDatasource(); validJNDIProp = (Properties)validJDBCProp.clone(); validJNDIProp.setProperty(KEY_DATABASE_ACCESS, "jndi"); validJNDIProp.setProperty(KEY_DATASOURCE_JNDI_NAME, "jdbc/MyDataSource"); validJNDIProp.remove(KEY_JDBC_URL); validJNDIProp.remove(KEY_JDBC_DRIVER); validJNDIProp.remove(KEY_DB_USERNAME); validJNDIProp.remove(KEY_DB_PASSWORD); incorrectDBAccessProp = (Properties)validJDBCProp.clone(); incorrectDBAccessProp.setProperty(KEY_DATABASE_ACCESS, "foo"); missingDBAccessProp = (Properties)validJDBCProp.clone(); missingDBAccessProp.remove(KEY_DATABASE_ACCESS); missingDatasourceJNDINameProp = (Properties)validJNDIProp.clone(); missingDatasourceJNDINameProp.remove(KEY_DATASOURCE_JNDI_NAME); wrongDatasourceJNDINameProp = (Properties)validJNDIProp.clone(); wrongDatasourceJNDINameProp.setProperty(KEY_DATASOURCE_JNDI_NAME, "foo"); noJdbcProp1 = (Properties)validJDBCProp.clone(); noJdbcProp1.remove(KEY_JDBC_DRIVER); noJdbcProp2 = (Properties)noJdbcProp1.clone(); noJdbcProp2.setProperty(KEY_JDBC_URL, "jdbc:foo:./test/db-test"); noJdbcProp3 = (Properties)noJdbcProp1.clone(); noJdbcProp3.remove(KEY_JDBC_URL); badJdbcProp = (Properties)validJDBCProp.clone(); badJdbcProp.setProperty(KEY_JDBC_DRIVER, "foo"); badJdbcProp.setProperty(KEY_JDBC_URL, "jdbc:foo:./test/db-test"); missingTranslatorProp = (Properties)validJDBCProp.clone(); missingTranslatorProp.remove(KEY_SQL_TRANSLATOR); badTranslatorProp = (Properties)validJDBCProp.clone(); badTranslatorProp.setProperty(KEY_SQL_TRANSLATOR, "foo"); badDBNameProp = (Properties)validJDBCProp.clone(); badDBNameProp.setProperty(KEY_JDBC_URL, "jdbc:h2:foo"); badUsernameProp = (Properties)validJDBCProp.clone(); badUsernameProp.setProperty(KEY_DB_USERNAME, "foo"); badPasswordProp = (Properties)validJDBCProp.clone(); badPasswordProp.setProperty(KEY_DB_PASSWORD, "foo"); validBackupFrequency = (Properties)validJDBCProp.clone(); validBackupFrequency.setProperty(KEY_BACKUP_FREQUENCY, "3600"); noBackup = (Properties)validJDBCProp.clone(); noBackup.setProperty(KEY_BACKUP_FREQUENCY, "never"); userBackup = (Properties)validJDBCProp.clone(); userBackup.setProperty(KEY_BACKUP_FREQUENCY, "user_action"); badBackupFrequency = (Properties)validJDBCProp.clone(); badBackupFrequency.setProperty(KEY_BACKUP_FREQUENCY, "foo"); } @AfterClass public static void tearDownAfterClass() throws Exception{ DBTools.dropTestDB(); } @Test public void testDefaultServiceConnection(){ // Correct Parameters (JDBC CASE): DBConnection connection = null; try{ TAPFactory factory = new ConfigurableTAPFactory(serviceConnection, validJDBCProp); connection = factory.getConnection("0"); assertNotNull(connection); assertNull(factory.createUWSBackupManager(new UWSService(factory, new LocalUWSFileManager(new File("."))))); }catch(Exception ex){ fail(getPertinentMessage(ex)); }finally{ if (connection != null){ try{ ((JDBCConnection)connection).getInnerConnection().close(); connection = null; }catch(SQLException se){} } } // Correct Parameters (JNDI CASE): try{ TAPFactory factory = new ConfigurableTAPFactory(serviceConnection, validJNDIProp); connection = factory.getConnection("0"); assertNotNull(connection); }catch(Exception ex){ fail(getPertinentMessage(ex)); }finally{ if (connection != null){ try{ ((JDBCConnection)connection).getInnerConnection().close(); connection = null; }catch(SQLException se){} } } // Incorrect database access method: try{ new ConfigurableServiceConnection(incorrectDBAccessProp); fail("This MUST have failed because the value of the property '" + KEY_DATABASE_ACCESS + "' is incorrect!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); assertEquals("Unsupported value for the property " + KEY_DATABASE_ACCESS + ": \"foo\"! Allowed values: \"" + VALUE_JNDI + "\" or \"" + VALUE_JDBC + "\".", e.getMessage()); } // Missing database access method: try{ new ConfigurableServiceConnection(missingDBAccessProp); fail("This MUST have failed because the property '" + KEY_DATABASE_ACCESS + "' is missing!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); assertEquals("The property \"" + KEY_DATABASE_ACCESS + "\" is missing! It is required to connect to the database. Two possible values: \"" + VALUE_JDBC + "\" and \"" + VALUE_JNDI + "\".", e.getMessage()); } // Missing JNDI name: try{ new ConfigurableServiceConnection(missingDatasourceJNDINameProp); fail("This MUST have failed because the property '" + KEY_DATASOURCE_JNDI_NAME + "' is missing!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); assertEquals("The property \"" + KEY_DATASOURCE_JNDI_NAME + "\" is missing! Since the choosen database access method is \"" + VALUE_JNDI + "\", this property is required.", e.getMessage()); } // Wrong JNDI name: try{ new ConfigurableServiceConnection(wrongDatasourceJNDINameProp); fail("This MUST have failed because the value of the property '" + KEY_DATASOURCE_JNDI_NAME + "' is incorrect!"); }catch(Exception e){ assertEquals(TAPException.class, e.getClass()); assertEquals("No datasource found with the JNDI name \"foo\"!", e.getMessage()); } // No JDBC Driver but the database type is known: try{ new ConfigurableTAPFactory(serviceConnection, noJdbcProp1); }catch(Exception ex){ fail(getPertinentMessage(ex)); } // No JDBC Driver but the database type is UNKNOWN: try{ new ConfigurableTAPFactory(serviceConnection, noJdbcProp2); fail("This MUST have failed because no JDBC Driver has been successfully guessed from the database type!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); assertTrue(ex.getMessage().matches("No JDBC driver known for the DBMS \"[^\\\"]*\"!")); } // Missing JDBC URL: try{ new ConfigurableTAPFactory(serviceConnection, noJdbcProp3); fail("This MUST have failed because the property \"" + KEY_JDBC_URL + "\" is missing!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); assertTrue(ex.getMessage().matches("The property \"" + KEY_JDBC_URL + "\" is missing! Since the choosen database access method is \"" + VALUE_JDBC + "\", this property is required.")); } // Bad JDBC Driver: try{ new ConfigurableTAPFactory(serviceConnection, badJdbcProp); fail("This MUST have failed because the provided JDBC Driver doesn't exist!"); }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); assertTrue(ex.getMessage().matches("Impossible to find the JDBC driver \"[^\\\"]*\" !")); } // Missing Translator: try{ new ConfigurableTAPFactory(serviceConnection, missingTranslatorProp); fail("This MUST have failed because the provided SQL translator is missing!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); assertTrue(ex.getMessage().matches("The property \"" + KEY_SQL_TRANSLATOR + "\" is missing! ADQL queries can not be translated without it. Allowed values: \"" + VALUE_POSTGRESQL + "\", \"" + VALUE_PGSPHERE + "\" or a class path of a class implementing SQLTranslator.")); } // Bad Translator: try{ new ConfigurableTAPFactory(serviceConnection, badTranslatorProp); fail("This MUST have failed because the provided SQL translator is incorrect!"); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); assertTrue(ex.getMessage().matches("Unsupported value for the property sql_translator: \"[^\\\"]*\" !")); } // Bad DB Name: try{ new ConfigurableTAPFactory(serviceConnection, badDBNameProp); fail("This MUST have failed because the provided database name is incorrect!"); }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\"!")); assertEquals(JdbcSQLException.class, ex.getCause().getClass()); assertEquals("A file path that is implicitly relative to the current working directory is not allowed in the database URL \"jdbc:h2:foo\". Use an absolute path, ~/name, ./name, or the baseDir setting instead. [90011-193]", ex.getCause().getMessage()); } // Bad DB Username: ABORTED BECAUSE THE BAD USERNAME IS NOT DETECTED FOR THE DB WHICH HAS THE SAME NAME AS THE USERNAME ! try{ new ConfigurableTAPFactory(serviceConnection, badUsernameProp); fail("This MUST have failed because the provided database username is incorrect!"); }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\"!")); assertEquals(JdbcSQLException.class, ex.getCause().getClass()); assertEquals("Wrong user name or password [28000-193]", ex.getCause().getMessage()); } // Bad DB Password: try{ new ConfigurableTAPFactory(serviceConnection, badPasswordProp); //fail("This MUST have failed because the provided database password is incorrect!"); // NOTE: In function of the database configuration, a password may be required or not. So this test is not automatic! }catch(Exception ex){ assertEquals(DBException.class, ex.getClass()); assertTrue(ex.getMessage().matches("Impossible to establish a connection to the database \"[^\\\"]*\"!")); assertEquals(JdbcSQLException.class, ex.getCause().getClass()); assertEquals("Wrong user name or password [28000-193]", ex.getCause().getMessage()); } // Valid backup frequency: try{ ConfigurableTAPFactory factory = new ConfigurableTAPFactory(serviceConnection, validBackupFrequency); DefaultTAPBackupManager backupManager = (DefaultTAPBackupManager)factory.createUWSBackupManager(new UWSService(factory, new LocalUWSFileManager(new File("/tmp")))); assertEquals(3600L, backupManager.getBackupFreq()); }catch(Exception ex){ fail(getPertinentMessage(ex)); } // No backup: try{ ConfigurableTAPFactory factory = new ConfigurableTAPFactory(serviceConnection, noBackup); assertNull(factory.createUWSBackupManager(new UWSService(factory, new LocalUWSFileManager(new File("/tmp"))))); }catch(Exception ex){ fail(getPertinentMessage(ex)); } // User backup: try{ UWSService uws; UserIdentifier userIdent = new UserIdentifier(){ private static final long serialVersionUID = 1L; @Override public JobOwner restoreUser(String id, String pseudo, Map<String,Object> otherData) throws UWSException{ return null; } @Override public JobOwner extractUserId(UWSUrl urlInterpreter, HttpServletRequest request) throws UWSException{ return null; } }; /* The value user_action has no effect if the by_user mode is not enabled. * So, if this value is given, it's falling back to manual.*/ userBackup.setProperty(KEY_BACKUP_BY_USER, "false"); ConfigurableTAPFactory factory = new ConfigurableTAPFactory(serviceConnection, userBackup); uws = new UWSService(factory, new LocalUWSFileManager(new File("/tmp"))); DefaultTAPBackupManager backupManager = (DefaultTAPBackupManager)factory.createUWSBackupManager(uws); assertEquals(DefaultTAPBackupManager.MANUAL, backupManager.getBackupFreq()); /* After having enabled the by_user mode, it should now work. */ userBackup.setProperty(KEY_BACKUP_BY_USER, "true"); factory = new ConfigurableTAPFactory(serviceConnection, userBackup); uws = new UWSService(factory, new LocalUWSFileManager(new File("/tmp"))); uws.setUserIdentifier(userIdent); backupManager = (DefaultTAPBackupManager)factory.createUWSBackupManager(uws); assertEquals(DefaultTAPBackupManager.AT_USER_ACTION, backupManager.getBackupFreq()); }catch(Exception ex){ fail(getPertinentMessage(ex)); } // Bad backup frequency: try{ new ConfigurableTAPFactory(serviceConnection, badBackupFrequency); }catch(Exception ex){ assertEquals(TAPException.class, ex.getClass()); assertEquals("Long expected for the property \"" + KEY_BACKUP_FREQUENCY + "\", instead of: \"foo\"!", ex.getMessage()); } } public static final String getPertinentMessage(final Exception ex){ return (ex.getCause() == null || ex.getMessage().equals(ex.getCause().getMessage())) ? ex.getMessage() : ex.getCause().getMessage(); } public static class ServiceConnectionTest implements ServiceConnection { private TAPLog logger = new DefaultTAPLog((UWSFileManager)null); private boolean isAvailable = true; @Override public String getProviderName(){ return null; } @Override public String getProviderDescription(){ return null; } @Override public boolean isAvailable(){ return isAvailable; } @Override public String getAvailability(){ return null; } @Override public int[] getRetentionPeriod(){ return null; } @Override public int[] getExecutionDuration(){ return null; } @Override public int[] getOutputLimit(){ return null; } @Override public tap.ServiceConnection.LimitUnit[] getOutputLimitType(){ return null; } @Override public UserIdentifier getUserIdentifier(){ return null; } @Override public boolean uploadEnabled(){ return false; } @Override public int[] getUploadLimit(){ return null; } @Override public tap.ServiceConnection.LimitUnit[] getUploadLimitType(){ return null; } @Override public int getMaxUploadSize(){ return 0; } @Override public TAPMetadata getTAPMetadata(){ return null; } @Override public Collection<String> getCoordinateSystems(){ return null; } @Override public TAPLog getLogger(){ return logger; } @Override public TAPFactory getFactory(){ return null; } @Override public UWSFileManager getFileManager(){ return null; } @Override public Iterator<OutputFormat> getOutputFormats(){ return null; } @Override public OutputFormat getOutputFormat(String mimeOrAlias){ return null; } @Override public void setAvailable(boolean isAvailable, String message){ this.isAvailable = isAvailable; } @Override public Collection<String> getGeometries(){ return null; } @Override public Collection<FunctionDef> getUDFs(){ return null; } @Override public int getNbMaxAsyncJobs(){ return -1; } @Override public int[] getFetchSize(){ return null; } } }