package com.revolsys.oracle.recordstore; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.sql.DataSource; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.jdbc.io.JdbcDatabaseFactory; import com.revolsys.jdbc.io.JdbcRecordStore; import com.revolsys.logging.Logs; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordStore; import com.revolsys.util.Property; import com.revolsys.util.Strings; /** * jdbc:oracle:thin:@//[host]:[port]/[ServiceName] * jdbc:oracle:thin:@[host]:[port]:[sid] jdbc:oracle:oci:@[tnsname] * jdbc:oracle:thin * :@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=[host])(PORT=[port * ]))(CONNECT_DATA=(SERVICE_NAME=[service]))) */ public class Oracle implements JdbcDatabaseFactory { private static final String REGEX_NAME = "[a-zA-Z0-9_\\$#\\.\\-]+"; private static final String REGEX_URL_PREFIX_USER_PASSWORD = "jdbc:oracle:(?:thin|oci):" // + "(" + REGEX_NAME + ")?" // Optional user name + "(?:/([^@]+))?" // Optional password + "@"; private static final Pattern URL_TNS_PATTERN = Pattern.compile(REGEX_URL_PREFIX_USER_PASSWORD + // "(" + REGEX_NAME + ")" // TNS Name ); private static final Pattern URL_HOST_PATTERN = Pattern .compile(REGEX_URL_PREFIX_USER_PASSWORD + "(?://)?" // + "([a-zA-Z-0-9][a-zA-Z-0-9\\.\\-]*)" // Host + "(?::(\\d+))?" // Optional Port Number + "[/:]" // Separator + "(" + REGEX_NAME + "+)" // SID or ArcGisRestService Name ); private static final List<FieldDefinition> CONNECTION_FIELD_DEFINITIONS = Arrays.asList( // new FieldDefinition("host", DataTypes.STRING, 50, true) // .setDefaultValue("localhost") // .addProperty(URL_FIELD, true), // new FieldDefinition("port", DataTypes.INTEGER, false) // .setMinValue(0) // .setMaxValue(65535) // .setDefaultValue(1521) // .addProperty(URL_FIELD, true), // new FieldDefinition("database", DataTypes.STRING, 64, true) // .addProperty(URL_FIELD, true), // new FieldDefinition("user", DataTypes.STRING, 30, false), // new FieldDefinition("password", DataTypes.STRING, 30, false) // ); public static List<String> getTnsConnectionNames() { File tnsFile = new File(System.getProperty("oracle.net.tns_admin"), "tnsnames.ora"); if (!tnsFile.exists()) { final String tnsAdmin = System.getenv("TNS_ADMIN"); if (tnsAdmin != null) { tnsFile = new File(tnsAdmin, "tnsnames.ora"); } if (!tnsFile.exists()) { final String oracleHome = System.getenv("ORACLE_HOME"); if (oracleHome != null) { tnsFile = new File(oracleHome + "/network/admin", "tnsnames.ora"); } if (!tnsFile.exists()) { if (oracleHome != null) { tnsFile = new File(oracleHome + "/NETWORK/ADMIN", "tnsnames.ora"); } } } } if (tnsFile.exists()) { try { final FileReader reader = new FileReader(tnsFile); final Class<?> parserClass = Class.forName("oracle.net.jdbc.nl.NLParamParser"); final Constructor<?> constructor = parserClass.getConstructor(Reader.class); final Object parser = constructor.newInstance(reader); final Method method = parserClass.getMethod("getNLPAllNames"); final List<String> names = new ArrayList<>(); for (final String name : (String[])method.invoke(parser)) { names.add(name.toLowerCase()); } return names; } catch (final NoSuchMethodException e) { } catch (final ClassNotFoundException e) { } catch (final InvocationTargetException e) { Logs.debug(Oracle.class, "Error reading: " + tnsFile, e.getCause()); } catch (final Throwable e) { Logs.debug(Oracle.class, "Error reading: " + tnsFile, e.getCause()); } } return Collections.emptyList(); } protected void addCacheProperty(final Map<String, Object> config, final String key, final Properties cacheProperties, final String propertyName, final Object defaultValue, final DataType dataType) { Object value = config.remove(key); if (value == null) { value = config.get(propertyName); } cacheProperties.put(propertyName, String.valueOf(defaultValue)); if (value != null) { try { final Object value1 = value; final Object propertyValue = dataType.toObject(value1); final String stringValue = String.valueOf(propertyValue); cacheProperties.put(propertyName, stringValue); } catch (final Throwable e) { } } } @Override public List<FieldDefinition> getConnectionFieldDefinitions() { return CONNECTION_FIELD_DEFINITIONS; } @Override public Map<String, String> getConnectionUrlMap() { final Map<String, String> connectionMap = new TreeMap<>(); for (final String connectionName : getTnsConnectionNames()) { final String connectionUrl = "jdbc:oracle:thin:@" + connectionName; connectionMap.put(connectionName, connectionUrl); } return connectionMap; } @Override public String getConnectionValidationQuery() { return "SELECT 1 FROM DUAL"; } @Override public String getDriverClassName() { return "oracle.jdbc.OracleDriver"; } @Override public String getName() { return "Oracle Database"; } @Override public String getProductName() { return "Oracle"; } @Override public Class<? extends RecordStore> getRecordStoreInterfaceClass( final Map<String, ? extends Object> connectionProperties) { return JdbcRecordStore.class; } @Override public String getVendorName() { return "oracle"; } @Override public JdbcRecordStore newRecordStore(final DataSource dataSource) { return new OracleRecordStore(dataSource); } @Override public JdbcRecordStore newRecordStore(final Map<String, ? extends Object> connectionProperties) { return new OracleRecordStore(this, connectionProperties); } @Override public Map<String, Object> parseUrl(final String url) { if (url != null && url.startsWith("jdbc:oracle")) { final Matcher hostMatcher = URL_HOST_PATTERN.matcher(url); final Map<String, Object> parameters = new LinkedHashMap<>(); if (hostMatcher.matches()) { parameters.put("recordStoreType", getName()); final String user = hostMatcher.group(1); if (Property.hasValue(user)) { parameters.put("user", user.toLowerCase()); } final String password = hostMatcher.group(2); if (Property.hasValue(password)) { parameters.put("password", password); } final String host = hostMatcher.group(3); parameters.put("host", host.toLowerCase()); final String port = hostMatcher.group(4); parameters.put("port", port); final String database = hostMatcher.group(5); parameters.put("database", Strings.lowerCase(database)); parameters.put("namedConnection", null); return parameters; } final Matcher tnsmatcher = URL_TNS_PATTERN.matcher(url); if (tnsmatcher.matches()) { parameters.put("recordStoreType", getProductName()); final String user = tnsmatcher.group(1); if (Property.hasValue(user)) { parameters.put("user", user.toLowerCase()); } final String password = tnsmatcher.group(2); if (Property.hasValue(password)) { parameters.put("password", password); } parameters.put("host", null); parameters.put("port", null); final String tnsname = tnsmatcher.group(3).toLowerCase(); if (getTnsConnectionNames().contains(tnsname)) { parameters.put("namedConnection", tnsname); parameters.put("database", null); } else { parameters.put("database", tnsname); parameters.put("namedConnection", null); } return parameters; } } return Collections.emptyMap(); } @Override public String toString() { return getName(); } @Override public String toUrl(final Map<String, Object> urlParameters) { final StringBuilder url = new StringBuilder("jdbc:oracle:thin:@"); final String namedConnection = Maps.getString(urlParameters, "namedConnection"); if (Property.hasValue(namedConnection)) { url.append(namedConnection.toLowerCase()); } else { final String host = Maps.getString(urlParameters, "host"); final Integer port = Maps.getInteger(urlParameters, "port"); final String database = Maps.getString(urlParameters, "database"); final boolean hasHost = Property.hasValue(host); final boolean hasPort = port != null; if (hasHost || hasPort) { url.append("//"); if (hasHost) { url.append(host); } if (hasPort) { url.append(':'); url.append(port); } url.append('/'); } if (Property.hasValue(database)) { url.append(database); } } return url.toString().toLowerCase(); } }