| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.payara.jakartaee.db; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import org.netbeans.modules.payara.common.PayaraState; |
| import org.netbeans.modules.payara.common.parser.TreeParser; |
| import org.netbeans.modules.payara.eecommon.api.UrlData; |
| import org.netbeans.modules.payara.eecommon.api.config.PayaraConfiguration; |
| import org.netbeans.modules.payara.jakartaee.ApplicationScopedResourcesUtils; |
| import org.netbeans.modules.payara.jakartaee.ApplicationScopedResourcesUtils.JndiNameResolver; |
| import org.netbeans.modules.payara.jakartaee.ApplicationScopedResourcesUtils.ResourceFileDescription; |
| import static org.netbeans.modules.payara.jakartaee.ApplicationScopedResourcesUtils.checkNamespaces; |
| import static org.netbeans.modules.payara.jakartaee.ApplicationScopedResourcesUtils.getJndiName; |
| import org.netbeans.modules.payara.jakartaee.Hk2DeploymentManager; |
| import org.netbeans.modules.payara.tooling.data.PayaraServer; |
| import org.netbeans.modules.payara.tooling.data.PayaraVersion; |
| import org.netbeans.modules.payara.tooling.utils.OsUtils; |
| import org.netbeans.modules.payara.tooling.utils.ServerUtils; |
| import org.netbeans.modules.j2ee.deployment.common.api.ConfigurationException; |
| import org.netbeans.modules.j2ee.deployment.common.api.Datasource; |
| import org.netbeans.modules.j2ee.deployment.common.api.DatasourceAlreadyExistsException; |
| import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eeModule; |
| import org.netbeans.modules.j2ee.deployment.plugins.spi.DatasourceManager; |
| import org.netbeans.modules.j2ee.sun.dd.api.RootInterface; |
| import org.netbeans.modules.javaee.specs.support.api.util.JndiNamespacesDefinition; |
| import org.openide.filesystems.FileLock; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileSystem; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Pair; |
| import org.openide.xml.XMLUtil; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.EntityResolver; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Payara server data source manager. |
| * <p/> |
| * @author Peter Williams, Tomas Kraus |
| */ |
| public class Hk2DatasourceManager implements DatasourceManager { |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Class attributes // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** Payara server domain configuration file. */ |
| private static final String DOMAIN_XML_PATH = OsUtils.joinPaths( |
| ServerUtils.PF_DOMAIN_CONFIG_DIR_NAME, |
| ServerUtils.PF_DOMAIN_CONFIG_FILE_NAME); |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Instance attributes // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** Payara server deployment manager. */ |
| private Hk2DeploymentManager dm; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Constructors // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Creates an instance of Payara server data source manager. |
| * <p/> |
| * @param dm Payara server deployment manager. |
| */ |
| public Hk2DatasourceManager(Hk2DeploymentManager dm) { |
| this.dm = dm; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Methods // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Retrieves the data sources deployed on the server. |
| * <p/> |
| * @return The set of data sources deployed on the server. |
| * @throws ConfigurationException reports problems in retrieving data source |
| * definitions. |
| */ |
| @Override |
| public Set<Datasource> getDatasources() throws ConfigurationException { |
| PayaraServer server = dm.getCommonServerSupport().getInstance(); |
| String domainsDir = server.getDomainsFolder(); |
| String domainName = server.getDomainName(); |
| // Try to retrieve data sources from asadmin interface if possible |
| if (PayaraState.isOnline(server) || server.isRemote()) { |
| DataSourcesReader dataSourcesReader |
| = new DataSourcesReader(server); |
| Set<Datasource> dataSources = dataSourcesReader.getDataSourcesFromServer(); |
| // Return when data sources were retrieved successfully. |
| if (dataSources != null) { |
| // #250444 we have to convert JDBCResource back to SunDatasource |
| // to have it all the same type in collections |
| return translate(dataSources); |
| } |
| } |
| // Fallback option to retrieve data sources from domain.xml |
| // for local server |
| if (!server.isRemote() && null != domainsDir) { |
| // XXX This won't read app scoped DS. This does not seem to be a problem. |
| File domainXml = new File(domainsDir, domainName + File.separatorChar + DOMAIN_XML_PATH); |
| return readDatasources( |
| domainXml, "/domain/", null, server.getVersion(), false); |
| } else { |
| return Collections.emptySet(); |
| } |
| } |
| |
| /** |
| * Deploys the given set of data sources. |
| * <p/> |
| * @param Set of datasources to deploy. |
| * @throws ConfigurationException if there is some problem with data source |
| * configuration. |
| * @throws DatasourceAlreadyExistsException if module data source(s) are |
| * conflicting with data source(s) already deployed on the server. |
| */ |
| @Override |
| public void deployDatasources(Set<Datasource> datasources) |
| throws ConfigurationException, DatasourceAlreadyExistsException { |
| // Since a connection pool is not a Datasource, the deploy has to |
| // happen in a different part of the deploy processing... |
| } |
| |
| /** |
| * Get {@link Datasource} objects from first available resources file. |
| * <p/> |
| * @param version Resources file names depend on Payara server version. |
| * @param module Java EE module (project). |
| * @return {@link Datasource} objects found in first available file. |
| */ |
| public static Set<Datasource> getDatasources( |
| final J2eeModule module, final PayaraVersion version) { |
| Pair<File, Boolean> result = PayaraConfiguration.getExistingResourceFile(module, version); |
| if (result != null) { |
| return readDatasources(result.first(), "/", module, version, result.second()); |
| } else { |
| return new HashSet<>(); |
| } |
| } |
| |
| private static Set<Datasource> translate(Collection<Datasource> sources) { |
| Set<Datasource> ret = new HashSet<>(); |
| for (Datasource ds : sources) { |
| ret.add(new SunDatasource(ds.getJndiName(), ds.getUrl(), ds.getUsername(), ds.getPassword(), ds.getDriverClassName())); |
| } |
| return ret; |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Internal logic |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Get resource file for new data source creation and verify it. |
| * Verifies existing resource file syntax and data sources or provides |
| * a new file when no resource file exists. |
| * <br/> |
| * <i>Internal {@link #createDataSource(String, String, String, |
| * String, String, J2eeModule, PayaraVersion)} helper method.</i> |
| * <p/> |
| * @param jndiName Data source JNDI name. |
| * @param url Database URL. |
| * @param username Database user name. |
| * @param password Database user password. |
| * @param driver Database JDBC driver. |
| * @param module Java EE module (project). |
| * @param version Payara server version. |
| * @param cpFinder Connection pool finder. |
| * @return Resource file for new data source creation. |
| * @throws ConfigurationException if there is a problem with resource |
| * file parsing. |
| * @throws DatasourceAlreadyExistsException if the required data source |
| * already exists in resource file. |
| */ |
| private static ApplicationScopedResourcesUtils.ResourceFileDescription resourceFileForDSCreation( |
| final String jndiName, final String url, final String username, |
| final String password, final String driver, final J2eeModule module, |
| final PayaraVersion version,final ConnectionPoolFinder cpFinder |
| ) throws ConfigurationException, DatasourceAlreadyExistsException { |
| Pair<File, Boolean> pair = PayaraConfiguration.getExistingResourceFile(module, version); |
| File file = pair == null ? null : pair.first(); |
| if (file != null && file.exists()) { |
| final DuplicateJdbcResourceFinder jdbcFinder |
| = new DuplicateJdbcResourceFinder(jndiName); |
| final JndiNamespaceFinder namespaceFinder = new JndiNamespaceFinder(); |
| List<TreeParser.Path> pathList = new ArrayList<>(); |
| pathList.add(new TreeParser.Path("/resources/jdbc-resource", |
| new ProxyNodeReader(jdbcFinder, namespaceFinder))); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/jdbc-connection-pool", |
| new ProxyNodeReader(cpFinder, namespaceFinder))); // NOI18N |
| |
| pathList.add(new TreeParser.Path("/resources/custom-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/mail-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/admin-object-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/connector-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/connector-connection-pool", namespaceFinder)); // NOI18N |
| try { |
| TreeParser.readXml(file, pathList); |
| if(jdbcFinder.isDuplicate()) { |
| throw new DatasourceAlreadyExistsException(new SunDatasource( |
| jndiName, url, username, password, driver)); |
| } |
| } catch(IllegalStateException ex) { |
| Logger.getLogger("payara-jakartaee").log( |
| Level.INFO, ex.getLocalizedMessage(), ex); |
| throw new ConfigurationException(ex.getLocalizedMessage(), ex); |
| } |
| return new ResourceFileDescription(file, pair.second(), namespaceFinder.getNamespaces()); |
| } |
| pair = PayaraConfiguration.getNewResourceFile(module, version); |
| return new ResourceFileDescription(pair.first(), pair.second(), Collections.<String>emptySet()); |
| } |
| |
| /** |
| * Create a data source (jdbc-resource and jdbc-connection-pool) and add it |
| * to sun-resources.xml in the specified resource folder. |
| * |
| * @param jndiName Data source JNDI name. |
| * @param url Database URL. |
| * @param username Database user name. |
| * @param password Database user password. |
| * @param driver Database JDBC driver. |
| * @param module Java EE module (project). |
| * @param version Payara server version. |
| * @return New {@link Datasource} object. |
| * @throws ConfigurationException if there is a problem with resource |
| * file parsing. |
| * @throws DatasourceAlreadyExistsException if the required data source |
| * already exists in resource file. |
| */ |
| public static Datasource createDataSource( |
| final String jndiName, final String url, final String username, |
| final String password, final String driver, |
| final J2eeModule module, final PayaraVersion version |
| ) throws ConfigurationException, DatasourceAlreadyExistsException { |
| SunDatasource ds; |
| ConnectionPoolFinder cpFinder = new ConnectionPoolFinder(); |
| |
| ResourceFileDescription fileDesc = resourceFileForDSCreation( |
| jndiName, url, username, password, driver, module, version, cpFinder); |
| |
| File xmlFile = fileDesc.getFile(); |
| try { |
| String vendorName = VendorNameMgr.vendorNameFromDbUrl(url); |
| if(vendorName == null) { |
| vendorName = jndiName; |
| } else { |
| if("derby_embedded".equals(vendorName)) { |
| // !PW FIXME display as dialog warning? |
| Logger.getLogger("payara-jakartaee").log(Level.WARNING, |
| "Embedded derby not supported as a datasource"); |
| return null; |
| } |
| } |
| |
| // Is there a connection pool we can reuse, or do we need to create one? |
| String defaultPoolName = computePoolName(url, vendorName, username); |
| Map<String, CPool> pools = cpFinder.getPoolData(); |
| CPool defaultPool = pools.get(defaultPoolName); |
| |
| String poolName = null; |
| if(defaultPool != null && isSameDatabaseConnection(defaultPool, url, username, password)) { |
| poolName = defaultPoolName; |
| } else { |
| for(CPool pool: pools.values()) { |
| if(isSameDatabaseConnection(pool, url, username, password)) { |
| poolName = pool.getPoolName(); |
| break; |
| } |
| } |
| } |
| |
| if (poolName == null) { |
| poolName = defaultPool == null ? defaultPoolName : generateUniqueName(defaultPoolName, pools.keySet()); |
| createConnectionPool(xmlFile, poolName, url, username, password, driver); |
| } |
| |
| fileDesc = checkNamespaces(module, fileDesc, JndiNamespacesDefinition.getNamespace(jndiName)); |
| String realJndiName = getJndiName(jndiName, fileDesc); |
| |
| // create jdbc resource |
| createJdbcResource(xmlFile, realJndiName, poolName); |
| |
| ds = new SunDatasource(realJndiName, url, username, password, driver, fileDesc.isIsApplicationScoped(), null); |
| } catch(IOException ex) { |
| Logger.getLogger("payara-jakartaee").log(Level.INFO, ex.getLocalizedMessage(), ex); |
| throw new ConfigurationException(ex.getLocalizedMessage(), ex); |
| } |
| |
| return ds; |
| } |
| |
| /** |
| * Parse resource file and build <code>Datasource</code> objects from it. |
| * <p/> |
| * For Payara server version 4 and higher new Java EE 7 |
| * <<code>comp/DefaultDataSource</code> data source is added if default |
| * <code>jdbc/__default</code> data source exists. This does not apply |
| * to calls with <code>null</code> value of <code>version</code> attribute. |
| * <p/> |
| * @param xmlFile Resource file to be read. This file must exists, and must |
| * be readable regular file containing XML. |
| * @param xPathPrefix XML path prefix, |
| * @param resourcesDir Directory containing resource files. |
| * @param version Payara server version to determine if Java EE 7 |
| * new data source shall be added. |
| * @return Data sources found in resource file. |
| */ |
| private static Set<Datasource> readDatasources( |
| final File xmlFile, final String xPathPrefix, |
| final J2eeModule module, final PayaraVersion version, boolean applicationScoped) { |
| final Set<Datasource> dataSources = new HashSet<>(); |
| |
| if (xmlFile.canRead()) { |
| final List<JdbcResource> jdbcResources = new LinkedList<>(); |
| final Map<String, ConnectionPool> connectionPoolMap = new HashMap<>(); |
| |
| final JndiNamespaceFinder namespaceFinder = new JndiNamespaceFinder(); |
| final List<TreeParser.Path> pathList = new ArrayList<>(); |
| pathList.add(new TreeParser.Path(xPathPrefix + "resources/jdbc-resource", |
| new ProxyNodeReader(new JdbcReader(jdbcResources), namespaceFinder))); |
| pathList.add(new TreeParser.Path(xPathPrefix + "resources/jdbc-connection-pool", |
| new ProxyNodeReader(new ConnectionPoolReader(connectionPoolMap), namespaceFinder))); |
| |
| pathList.add(new TreeParser.Path("/resources/custom-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/mail-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/admin-object-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/connector-resource", namespaceFinder)); // NOI18N |
| pathList.add(new TreeParser.Path("/resources/connector-connection-pool", namespaceFinder)); // NOI18N |
| try { |
| TreeParser.readXml(xmlFile, pathList); |
| } catch(IllegalStateException ex) { |
| Logger.getLogger("payara-jakartaee").log(Level.INFO, ex.getLocalizedMessage(), ex); |
| } |
| |
| ApplicationScopedResourcesUtils.ResourceFileDescription fileDesc = new ApplicationScopedResourcesUtils.ResourceFileDescription( |
| xmlFile, applicationScoped, namespaceFinder.getNamespaces()); |
| JndiNameResolver resolver = applicationScoped && module != null ? new UserResolver(module, fileDesc) : null; |
| for (JdbcResource jdbc : jdbcResources) { |
| final ConnectionPool pool = connectionPoolMap.get(jdbc.getPoolName()); |
| if (pool != null) { |
| try { |
| pool.normalize(); |
| |
| // add to sun datasource list |
| final String url = pool.getProperty("URL"); //NOI18N |
| final String username = pool.getProperty("User"); //NOI18N |
| final String password = pool.getProperty("Password"); //NOI18N |
| final String driverClassName = pool.getProperty("driverClass"); //NOI18N |
| |
| final SunDatasource dataSource = new SunDatasource( |
| jdbc.getJndiName(), url, username, password, driverClassName, applicationScoped, resolver); |
| dataSources.add(dataSource); |
| // Add Java EE 7 comp/DefaultDataSource data source |
| // as jdbc/__default clone (since PF 4). |
| if (version != null && version.ordinal() |
| >= PayaraVersion.PF_4_1_144.ordinal() |
| && DataSourcesReader.DEFAULT_DATA_SOURCE |
| .equals(jdbc.getJndiName()) ) { |
| dataSources.add(dataSource.copy( |
| DataSourcesReader.DEFAULT_DATA_SOURCE_EE7)); |
| } |
| } catch (NullPointerException npe) { |
| Logger.getLogger("payara-jakartaee").log(Level.INFO, pool.toString(), npe); |
| } |
| } |
| } |
| } |
| return dataSources; |
| } |
| |
| private static class JdbcResource { |
| |
| private final String jndiName; |
| private final String poolName; |
| |
| public JdbcResource(String jndiName) { |
| this(jndiName, ""); |
| } |
| |
| public JdbcResource(String jndiName, String poolName) { |
| this.jndiName = jndiName; |
| this.poolName = poolName; |
| } |
| |
| public String getJndiName() { |
| return jndiName; |
| } |
| |
| public String getPoolName() { |
| return poolName; |
| } |
| } |
| |
| private static class JdbcReader extends TreeParser.NodeReader { |
| |
| private final List<JdbcResource> resources; |
| |
| public JdbcReader(List<JdbcResource> resources) { |
| this.resources = resources; |
| } |
| |
| // <jdbc-resource |
| // enabled="true" |
| // pool-name="DerbyPool" |
| // jndi-name="jdbc/__default" |
| // object-type="user" /> |
| |
| @Override |
| public void readAttributes(String qname, Attributes attributes) throws SAXException { |
| String type = attributes.getValue("object-type"); |
| |
| String jndiName = attributes.getValue("jndi-name"); |
| String poolName = attributes.getValue("pool-name"); |
| if(jndiName != null && jndiName.length() > 0 && |
| poolName != null && poolName.length() > 0) { |
| // add to jdbc resource list |
| resources.add( |
| new JdbcResource(jndiName, poolName)); |
| } |
| } |
| } |
| |
| private static class ConnectionPool { |
| |
| private final Map<String, String> properties; |
| |
| public ConnectionPool(String poolName) { |
| this.properties = new HashMap<>(); |
| } |
| |
| public void setProperty(String key, String value) { |
| properties.put(key, value); |
| } |
| |
| public String getProperty(String key) { |
| return properties.get(key); |
| } |
| |
| public void normalize() { |
| DbUtil.normalizePoolMap(properties); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Hk2DatasourceManager$ConnectionPool["); |
| for (Entry<String,String> e : properties.entrySet()) { |
| sb.append(e.getKey()); |
| sb.append("="); |
| sb.append(e.getValue()); |
| sb.append("--%%--"); |
| } |
| return sb.append("]").toString(); |
| } |
| |
| } |
| |
| private static class ConnectionPoolReader extends TreeParser.NodeReader { |
| |
| private Map<String, ConnectionPool> resourceMap; |
| private ConnectionPool currentPool; |
| |
| public ConnectionPoolReader(Map<String, ConnectionPool> resourceMap) { |
| this.resourceMap = resourceMap; |
| } |
| |
| //<jdbc-connection-pool |
| // datasource-classname="org.apache.derby.jdbc.ClientDataSource" |
| // name="DerbyPool" |
| // res-type="javax.sql.DataSource" |
| // <property name="PortNumber" value="1527" /> |
| // <property name="Password" value="APP" /> |
| // <property name="User" value="APP" /> |
| // <property name="serverName" value="localhost" /> |
| // <property name="DatabaseName" value="sun-appserv-samples" /> |
| // <property name="connectionAttributes" value=";create=true" /> |
| //</jdbc-connection-pool> |
| |
| @Override |
| public void readAttributes(String qname, Attributes attributes) throws SAXException { |
| String poolName = attributes.getValue("name"); |
| if(poolName != null && poolName.length() > 0) { |
| currentPool = new ConnectionPool(poolName); |
| currentPool.setProperty("dsClassName", attributes.getValue("datasource-classname")); |
| currentPool.setProperty("resType", attributes.getValue("res-type")); |
| resourceMap.put(poolName, currentPool); |
| } else { |
| currentPool = null; |
| } |
| } |
| |
| @Override |
| public void readChildren(String qname, Attributes attributes) throws SAXException { |
| if(currentPool != null) { |
| String key = attributes.getValue("name"); |
| if(key != null && key.length() > 0) { |
| currentPool.setProperty(key, attributes.getValue("value")); |
| } |
| } |
| } |
| } |
| |
| private static String generateUniqueName(String prefix, Set<String> keys) { |
| for(int i = 1; ; i++) { |
| String candidate = prefix + "_" + i; // NOI18N |
| if(!keys.contains(candidate)) { |
| return candidate; |
| } |
| } |
| } |
| |
| private static boolean isSameDatabaseConnection(final CPool pool, final String url, |
| final String username, final String password) { |
| boolean result = false; |
| boolean matchedSettings; |
| |
| UrlData urlData = new UrlData(url); |
| if(DbUtil.strEmpty(pool.getUrl())) { |
| matchedSettings = DbUtil.strEquivalent(urlData.getHostName(), pool.getHostname()) && |
| DbUtil.strEquivalent(urlData.getPort(), pool.getPort()) && |
| DbUtil.strEquivalent(urlData.getDatabaseName(), pool.getDatabaseName()) && |
| DbUtil.strEquivalent(urlData.getSid(), pool.getSid()); |
| } else { |
| matchedSettings = DbUtil.strEquivalent(url, pool.getUrl()); |
| } |
| |
| if(matchedSettings) { |
| if(DbUtil.strEquivalent(username, pool.getUsername()) && |
| DbUtil.strEquivalent(password, pool.getPassword())) { |
| result = true; |
| } |
| } |
| |
| return result; |
| } |
| |
| private static final String CP_TAG_1 = |
| " <jdbc-connection-pool " + |
| "allow-non-component-callers=\"false\" " + |
| "associate-with-thread=\"false\" " + |
| "connection-creation-retry-attempts=\"0\" " + |
| "connection-creation-retry-interval-in-seconds=\"10\" " + |
| "connection-leak-reclaim=\"false\" " + |
| "connection-leak-timeout-in-seconds=\"0\" " + |
| "connection-validation-method=\"auto-commit\" "; |
| |
| // "datasource-classname=\"org.postgresql.ds.PGSimpleDataSource\" " + |
| private static final String ATTR_DATASOURCE_CLASSNAME = "datasource-classname"; |
| private static final String CP_TAG_2 = |
| "fail-all-connections=\"false\" " + |
| "idle-timeout-in-seconds=\"300\" " + |
| "is-connection-validation-required=\"false\" " + |
| "is-isolation-level-guaranteed=\"true\" " + |
| "lazy-connection-association=\"false\" " + |
| "lazy-connection-enlistment=\"false\" " + |
| "match-connections=\"false\" " + |
| "max-connection-usage-count=\"0\" " + |
| "max-pool-size=\"32\" " + |
| "max-wait-time-in-millis=\"60000\" "; |
| // "name=\"sawhorse-pool\" " + |
| private static final String ATTR_POOL_NAME = "name"; |
| private static final String CP_TAG_3 = |
| "non-transactional-connections=\"false\" " + |
| "pool-resize-quantity=\"2\" "; |
| // "res-type=\"javax.sql.DataSource\" " + |
| private static final String ATTR_RES_TYPE = "res-type"; |
| private static final String CP_TAG_4 = |
| "statement-timeout-in-seconds=\"-1\" " + |
| "steady-pool-size=\"8\" " + |
| "validate-atmost-once-period-in-seconds=\"0\" " + |
| "wrap-jdbc-objects=\"false\">\n"; |
| private static final String PROP_SERVER_NAME = "serverName"; |
| private static final String PROP_PORT_NUMBER = "portNumber"; |
| private static final String PROP_DATABASE_NAME = "databaseName"; |
| private static final String PROP_USER = "User"; |
| private static final String PROP_PASSWORD = "Password"; |
| private static final String PROP_URL = "URL"; |
| private static final String PROP_DRIVER_CLASS = "driverClass"; |
| private static final String CP_TAG_5 = " </jdbc-connection-pool>\n"; |
| |
| private static final String RESTYPE_DATASOURCE = "javax.sql.DataSource"; |
| |
| public static void createConnectionPool(File sunResourcesXml, String poolName, |
| String url, String username, String password, String driver) throws IOException { |
| |
| // <jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="org.postgresql.ds.PGSimpleDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="sawhorse-pool" non-transactional-connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false"> |
| // <property name="serverName" value="localhost"/> |
| // <property name="portNumber" value="5432"/> |
| // <property name="databaseName" value="cookbook2_development"/> |
| // <property name="User" value="cookbook2"/> |
| // <property name="Password" value="cookbook2"/> |
| // <property name="URL" value="jdbc:postgresql://localhost:5432/cookbook2_development"/> |
| // <property name="driverClass" value="org.postgresql.Driver"/> |
| // </jdbc-connection-pool> |
| DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); |
| DocumentBuilder docBuilder = null; |
| Document doc = null; |
| try { |
| docBuilder = docFactory.newDocumentBuilder(); |
| docBuilder.setEntityResolver(DDResolver.getInstance()); |
| doc = readResourceFile(docBuilder, sunResourcesXml); |
| } catch (ParserConfigurationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| |
| if (doc == null || docBuilder == null) return; |
| NodeList resourcesNodes = doc.getElementsByTagName("resources");//NOI18N |
| Node resourcesNode; |
| if(resourcesNodes.getLength()<1){ |
| resourcesNode = doc.createElement("resources");//NOI18N |
| doc.getDocumentElement().appendChild(resourcesNode); |
| } else { |
| resourcesNode = resourcesNodes.item(0); |
| } |
| Node newPool; |
| try { |
| newPool = resourcesNode.appendChild(doc.importNode(docBuilder.parse(new InputSource(new StringReader(CP_TAG_1+CP_TAG_2+CP_TAG_3+CP_TAG_4+CP_TAG_5))).getDocumentElement(), true)); |
| UrlData urlData = new UrlData(url); |
| |
| // Maybe move this logic into UrlData? |
| String dsClassName = computeDataSourceClassName(url, driver); |
| |
| appendAttr(doc, newPool, ATTR_DATASOURCE_CLASSNAME, dsClassName, false); |
| appendAttr(doc, newPool, ATTR_POOL_NAME, poolName, true); |
| appendAttr(doc, newPool, ATTR_RES_TYPE, RESTYPE_DATASOURCE, true); |
| appendProperty(doc, newPool, PROP_SERVER_NAME, urlData.getHostName(), true); |
| appendProperty(doc, newPool, PROP_PORT_NUMBER, urlData.getPort(), false); |
| appendProperty(doc, newPool, PROP_DATABASE_NAME, urlData.getDatabaseName(), false); |
| appendProperty(doc, newPool, PROP_USER, username, true); |
| // blank password is ok so check just null here and pass force=true. |
| if(password != null) { |
| appendProperty(doc, newPool, PROP_PASSWORD, password, true); |
| } |
| appendProperty(doc, newPool, PROP_URL, url, true); |
| appendProperty(doc, newPool, PROP_DRIVER_CLASS, driver, true); |
| |
| Logger.getLogger("payara-jakartaee").log(Level.FINER, |
| "New connection pool resource:\n{0}", newPool.getTextContent()); |
| } catch (SAXException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| |
| writeXmlResourceToFile(sunResourcesXml, doc); |
| } |
| |
| private static String computeDataSourceClassName(String url, String driver) { |
| String vendorName = VendorNameMgr.vendorNameFromDbUrl(url); |
| String dsClassName = VendorNameMgr.dsClassNameFromVendorName(vendorName); |
| |
| if(dsClassName == null || dsClassName.length() == 0) { |
| dsClassName = DriverMaps.getDSClassName(url); |
| if(dsClassName == null || dsClassName.length() == 0) { |
| dsClassName = driver; |
| } |
| } |
| |
| return dsClassName; |
| } |
| |
| private static String computePoolName(String url, String vendorName, String username){ |
| UrlData urlData = new UrlData(url); |
| StringBuilder poolName = new StringBuilder(vendorName); |
| String dbName = getDatabaseName(urlData); |
| if (dbName != null) { |
| poolName.append("_").append(dbName); //NOI18N |
| } |
| if (username != null) { |
| poolName.append("_").append(username); //NOI18N |
| } |
| poolName.append("Pool"); //NOI18N |
| return poolName.toString(); |
| } |
| |
| private static String getDatabaseName(UrlData urlData) { |
| String databaseName = urlData.getDatabaseName(); |
| if (databaseName == null) { |
| databaseName = urlData.getAlternateDBName(); |
| } |
| |
| return databaseName; |
| } |
| |
| private static final String JDBC_TAG_1 = |
| " <jdbc-resource " + |
| "enabled=\"true\" "; |
| // pool-name="DerbyPool" |
| private static final String ATTR_POOLNAME = "pool-name"; |
| // jndi-name="jdbc/__default" |
| private static final String ATTR_JNDINAME = "jndi-name"; |
| private static final String JDBC_TAG_2 = |
| " object-type=\"user\"/>\n"; |
| |
| public static void createJdbcResource(File sunResourcesXml, String jndiName, String poolName) throws IOException { |
| |
| // <jdbc-resource |
| // enabled="true" |
| // pool-name="DerbyPool" |
| // jndi-name="jdbc/__default" |
| // object-type="user" /> |
| |
| DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); |
| DocumentBuilder docBuilder = null; |
| Document doc = null; |
| try { |
| docBuilder = docFactory.newDocumentBuilder(); |
| docBuilder.setEntityResolver(DDResolver.getInstance()); |
| doc = readResourceFile(docBuilder, sunResourcesXml); |
| } catch (ParserConfigurationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| if (doc == null || docBuilder == null) { |
| return; |
| } |
| NodeList resourcesNodes = doc.getElementsByTagName("resources");//NOI18N |
| Node resourcesNode = null; |
| if(resourcesNodes.getLength()<1){ |
| throw new IOException("Malformed XML"); |
| } else { |
| resourcesNode = resourcesNodes.item(0); |
| } |
| Node newJdbcRes; |
| try { |
| newJdbcRes = resourcesNode.appendChild(doc.importNode( |
| docBuilder.parse(new InputSource(new StringReader(JDBC_TAG_1+JDBC_TAG_2))).getDocumentElement(), true)); |
| appendAttr(doc, newJdbcRes, ATTR_POOLNAME, poolName, true); |
| appendAttr(doc, newJdbcRes, ATTR_JNDINAME, jndiName, true); |
| |
| Logger.getLogger("payara-jakartaee").log(Level.FINER, |
| "New JDBC resource:\n{0}", newJdbcRes.getTextContent()); |
| } catch (SAXException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| writeXmlResourceToFile(sunResourcesXml, doc); |
| } |
| |
| private static void appendAttr(Document doc, Node node, String name, String value, boolean force) { |
| if(force || (name != null && name.length() > 0)) { |
| Attr attr = doc.createAttribute(name); |
| attr.setValue(value); |
| NamedNodeMap attrs = node.getAttributes(); |
| attrs.setNamedItem(attr); |
| } |
| } |
| |
| private static void appendProperty(Document doc, Node node, String name, String value, boolean force) { |
| if(force || (value != null && value.length() > 0)) { |
| Node newProperty = doc.createElement("property");//NOI18N |
| Attr nameAttr = doc.createAttribute("name");//NOI18N |
| nameAttr.setValue(name); |
| Attr valueAttr = doc.createAttribute("value");//NOI18N |
| valueAttr.setValue(value); |
| NamedNodeMap attrs = newProperty.getAttributes(); |
| attrs.setNamedItem(nameAttr); |
| attrs.setNamedItem(valueAttr); |
| node.appendChild(newProperty); |
| } |
| } |
| |
| private static final String SUN_RESOURCES_XML_HEADER = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + |
| "<!DOCTYPE resources PUBLIC " + |
| "\"-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN\" " + |
| "\"http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd\">\n" + |
| "<resources/>"; |
| |
| private static final String GF_RESOURCES_XML_HEADER = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + |
| "<!DOCTYPE resources PUBLIC " +"\"-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN\" " + |
| "\"http://glassfish.org/dtds/glassfish-resources_1_5.dtd\">\n" + |
| // "\"-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN\" " + |
| // "\"http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd\">\n" + |
| "<resources/>"; |
| |
| private static Document readResourceFile(DocumentBuilder docBuilder, File sunResourcesXml) throws IOException{ |
| boolean newOne = false; |
| if(!sunResourcesXml.exists()){ |
| FileUtil.createData(sunResourcesXml);//ensure file exist |
| newOne = true; |
| } |
| Document doc = null; |
| try { |
| if(newOne) { |
| if (sunResourcesXml.getAbsolutePath().contains("sun-resources.xml")) |
| doc = docBuilder.parse(new InputSource(new StringReader(SUN_RESOURCES_XML_HEADER))); |
| else |
| doc = docBuilder.parse(new InputSource(new StringReader(GF_RESOURCES_XML_HEADER))); |
| } |
| else { |
| doc = docBuilder.parse(sunResourcesXml); |
| } |
| } catch (SAXException ex) { |
| throw new IOException("Malformed XML: " +ex.getMessage()); |
| } |
| |
| return doc; |
| } |
| |
| |
| private static void writeXmlResourceToFile(final File sunResourcesXml, final Document doc) throws IOException { |
| |
| FileObject parentFolder = FileUtil.createFolder(sunResourcesXml.getParentFile()); |
| FileSystem fs = parentFolder.getFileSystem(); |
| |
| fs.runAtomicAction(new FileSystem.AtomicAction() { |
| @Override |
| public void run() throws IOException { |
| FileLock lock = null; |
| OutputStream os = null; |
| try { |
| FileObject sunResourcesFO = FileUtil.createData(sunResourcesXml); |
| lock = sunResourcesFO.lock(); |
| os = sunResourcesFO.getOutputStream(lock); |
| |
| XMLUtil.write(doc, os, doc.getXmlEncoding()); |
| } finally { |
| if(os !=null ){ |
| os.close(); |
| } |
| if(lock != null) { |
| lock.releaseLock(); |
| } |
| } |
| } |
| }); |
| } |
| |
| private static class UserResolver implements JndiNameResolver { |
| |
| private final J2eeModule module; |
| |
| private ApplicationScopedResourcesUtils.ResourceFileDescription fileDesc; |
| |
| public UserResolver(J2eeModule module, ApplicationScopedResourcesUtils.ResourceFileDescription fileDesc) { |
| this.module = module; |
| this.fileDesc = fileDesc; |
| } |
| |
| @Override |
| public synchronized String resolveJndiName(String jndiName) { |
| fileDesc = ApplicationScopedResourcesUtils.checkNamespaces(module, fileDesc, |
| JndiNamespacesDefinition.getNamespace(jndiName)); |
| String jndi = ApplicationScopedResourcesUtils.getJndiName(jndiName, fileDesc); |
| return jndi; |
| } |
| } |
| |
| private static class DuplicateJdbcResourceFinder extends TreeParser.NodeReader { |
| |
| private final String targetJndiName; |
| private boolean duplicate; |
| private String poolName; |
| |
| public DuplicateJdbcResourceFinder(String jndiName) { |
| targetJndiName = jndiName; |
| duplicate = false; |
| poolName = null; |
| } |
| |
| @Override |
| public void readAttributes(String qname, Attributes attributes) throws SAXException { |
| String jndiName = attributes.getValue("jndi-name"); |
| if(targetJndiName.equals(jndiName)) { |
| if(duplicate) { |
| Logger.getLogger("payara-jakartaee").log(Level.WARNING, |
| "Duplicate jndi-names defined for JDBC resources."); |
| } |
| duplicate = true; |
| poolName = attributes.getValue("pool-name"); |
| } |
| } |
| |
| public boolean isDuplicate() { |
| return duplicate; |
| } |
| |
| public String getPoolName() { |
| return poolName; |
| } |
| |
| } |
| |
| private static class ConnectionPoolFinder extends TreeParser.NodeReader { |
| |
| private Map<String, String> properties = null; |
| private Map<String, CPool> pools = new HashMap<>(); |
| |
| @Override |
| public void readAttributes(String qname, Attributes attributes) throws SAXException { |
| properties = new HashMap<>(); |
| |
| String poolName = attributes.getValue("name"); |
| if(poolName != null && poolName.length() > 0) { |
| if(!pools.containsKey(poolName)) { |
| properties.put("name", poolName); |
| } else { |
| Logger.getLogger("payara-jakartaee").log(Level.WARNING, |
| "Duplicate pool-names defined for JDBC Connection Pools."); |
| } |
| } |
| } |
| |
| @Override |
| public void readChildren(String qname, Attributes attributes) throws SAXException { |
| if (null != attributes && null != properties) { |
| String key = attributes.getValue("name"); // NOI18N |
| if(key != null && key.length() > 0) { |
| properties.put(key.toLowerCase(Locale.ENGLISH), attributes.getValue("value")); // NOI18N |
| } |
| } |
| } |
| |
| @Override |
| public void endNode(String qname) throws SAXException { |
| String poolName = properties.get("name"); |
| CPool pool = new CPool( |
| poolName, |
| properties.get("url"), |
| properties.get("servername"), |
| properties.get("portnumber"), |
| properties.get("databasename"), |
| properties.get("user"), |
| properties.get("password"), |
| properties.get("connectionattributes") |
| ); |
| pools.put(poolName, pool); |
| } |
| |
| public List<String> getPoolNames() { |
| return new ArrayList<>(pools.keySet()); |
| } |
| |
| public Map<String, CPool> getPoolData() { |
| return Collections.unmodifiableMap(pools); |
| } |
| |
| } |
| |
| private static class CPool { |
| |
| private final String poolName; |
| private final String url; |
| private final String hostname; |
| private final String port; |
| private final String databaseName; |
| private final String username; |
| private final String password; |
| private final String sid; |
| |
| public CPool(String poolName, String url, String hostName, String port, |
| String databaseName, String username, String password, String sid) { |
| this.poolName = poolName; |
| this.url = url; |
| this.hostname = hostName; |
| this.port = port; |
| this.databaseName = databaseName; |
| this.username = username; |
| this.password = password; |
| this.sid = sid; |
| } |
| |
| public String getPoolName() { |
| return poolName; |
| } |
| |
| public String getUrl() { |
| return url; |
| } |
| |
| public String getDatabaseName() { |
| return databaseName; |
| } |
| |
| public String getHostname() { |
| return hostname; |
| } |
| |
| public String getPassword() { |
| return password; |
| } |
| |
| public String getPort() { |
| return port; |
| } |
| |
| public String getSid() { |
| return sid; |
| } |
| |
| public String getUsername() { |
| return username; |
| } |
| |
| } |
| |
| private static class JndiNamespaceFinder extends TreeParser.NodeReader { |
| |
| private final Set<String> namespaces = new HashSet<>(); |
| |
| @Override |
| public void readAttributes(String qname, Attributes attributes) throws SAXException { |
| String jndiName = attributes.getValue("jndi-name"); |
| if (jndiName == null) { |
| jndiName = attributes.getValue("name"); |
| } |
| if (jndiName != null) { |
| String ns = JndiNamespacesDefinition.getNamespace(jndiName); |
| if (ns != null) { |
| namespaces.add(ns); |
| } |
| } |
| } |
| |
| public Set<String> getNamespaces() { |
| return namespaces; |
| } |
| } |
| |
| private static class ProxyNodeReader extends TreeParser.NodeReader { |
| |
| private final TreeParser.NodeReader[] readers; |
| |
| public ProxyNodeReader(TreeParser.NodeReader... readers) { |
| this.readers = readers; |
| } |
| |
| @Override |
| public void endNode(String qname) throws SAXException { |
| for (TreeParser.NodeReader r : readers) { |
| r.endNode(qname); |
| } |
| } |
| |
| @Override |
| public void readCData(String qname, char[] ch, int start, int length) throws SAXException { |
| for (TreeParser.NodeReader r : readers) { |
| r.readCData(qname, ch, start, length); |
| } |
| } |
| |
| @Override |
| public void readChildren(String qname, Attributes attributes) throws SAXException { |
| for (TreeParser.NodeReader r : readers) { |
| r.readChildren(qname, attributes); |
| } |
| } |
| |
| @Override |
| public void readAttributes(String qname, Attributes attributes) throws SAXException { |
| for (TreeParser.NodeReader r : readers) { |
| r.readAttributes(qname, attributes); |
| } |
| } |
| } |
| |
| private static class DDResolver implements EntityResolver { |
| static DDResolver resolver; |
| static synchronized DDResolver getInstance() { |
| if (resolver==null) { |
| resolver=new DDResolver(); |
| } |
| return resolver; |
| } |
| |
| @Override |
| public InputSource resolveEntity(String publicId, String systemId) { |
| |
| String resource=null; |
| // return a proper input source |
| if (systemId!=null && systemId.endsWith("sun-resources_1_3.dtd")) { |
| resource="/org/netbeans/modules/j2ee/sun/dd/impl/resources/sun-resources_1_3.dtd"; //NOI18N |
| } |
| if (systemId!=null && systemId.endsWith("glassfish-resources_1_5.dtd")) { |
| resource="/org/netbeans/modules/j2ee/sun/dd/impl/resources/glassfish-resources_1_5.dtd"; //NOI18N |
| } |
| |
| if (resource==null) { |
| return null; |
| } |
| java.net.URL url = RootInterface.class.getResource(resource); |
| return new InputSource(url.toString()); |
| } |
| } |
| } |