X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=runtime%2Forg.argeo.slc.akb%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fslc%2Fakb%2Fcore%2FAkbServiceImpl.java;h=83de7294c8b96b0c8d7cdc49d92dcd87d8460a5b;hb=44b4c78e6a082d51d0410c3995b5edf353d417b9;hp=e819bbfa82a858803de4e44e89b7bce31aa4cf36;hpb=cce1ca8a555b41f6ca0a77ab549a5e1461f2bb15;p=gpl%2Fargeo-slc.git diff --git a/runtime/org.argeo.slc.akb/src/main/java/org/argeo/slc/akb/core/AkbServiceImpl.java b/runtime/org.argeo.slc.akb/src/main/java/org/argeo/slc/akb/core/AkbServiceImpl.java index e819bbfa8..83de7294c 100644 --- a/runtime/org.argeo.slc.akb/src/main/java/org/argeo/slc/akb/core/AkbServiceImpl.java +++ b/runtime/org.argeo.slc.akb/src/main/java/org/argeo/slc/akb/core/AkbServiceImpl.java @@ -1,21 +1,46 @@ package org.argeo.slc.akb.core; +import java.net.URI; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLFeatureNotSupportedException; import java.util.Map; import javax.annotation.Resource; import javax.jcr.Node; +import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; +import javax.jcr.query.qom.Constraint; +import javax.jcr.query.qom.Ordering; +import javax.jcr.query.qom.QueryObjectModel; +import javax.jcr.query.qom.QueryObjectModelConstants; +import javax.jcr.query.qom.QueryObjectModelFactory; +import javax.jcr.query.qom.Selector; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; +import org.argeo.slc.SlcException; import org.argeo.slc.akb.AkbException; import org.argeo.slc.akb.AkbNames; import org.argeo.slc.akb.AkbService; import org.argeo.slc.akb.AkbTypes; +import org.argeo.slc.akb.utils.AkbJcrUtils; +import org.argeo.slc.jsch.SimpleUserInfo; +import org.argeo.util.security.Keyring; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSch; /** * Concrete access to akb services. It provides among other an initialized @@ -27,6 +52,8 @@ public class AkbServiceImpl implements AkbService, AkbNames { /* DEPENDENCY INJECTION */ private Repository repository; + private Keyring keyring; + // Populate the repository in a demo context. private Map demoData = null; @@ -36,6 +63,10 @@ public class AkbServiceImpl implements AkbService, AkbNames { * receive/provide data. */ public void init() { + // JDBC drivers + // TODO make it configurable + initJdbcDriver("org.postgresql.Driver"); + Session adminSession = null; try { adminSession = repository.login(); @@ -62,6 +93,18 @@ public class AkbServiceImpl implements AkbService, AkbNames { } } + protected Boolean initJdbcDriver(String driver) { + try { + Class.forName(driver); + return true; + } catch (ClassNotFoundException e) { + if (log.isDebugEnabled()) + log.debug("Cannot load JDBC driver : " + driver + ", " + + e.getMessage()); + return false; + } + } + /** Clean shutdown of the backend. */ public void destroy() { } @@ -69,35 +112,421 @@ public class AkbServiceImpl implements AkbService, AkbNames { @Override public Node createAkbTemplate(Node parentNode, String name) throws RepositoryException { - String connectorParentName = "Connectors"; - String itemsParentName = "Items"; - Node newTemplate = parentNode.addNode(name, AkbTypes.AKB_ENV_TEMPLATE); newTemplate.setProperty(Property.JCR_TITLE, name); - Node connectorParent = newTemplate.addNode( - AkbTypes.AKB_CONNECTOR_FOLDER, AkbTypes.AKB_CONNECTOR_FOLDER); - connectorParent.setProperty(Property.JCR_TITLE, connectorParentName); - - Node itemsParent = newTemplate.addNode(AkbTypes.AKB_ITEM_FOLDER, - AkbTypes.AKB_ITEM_FOLDER); - itemsParent.setProperty(Property.JCR_TITLE, itemsParentName); + // Node connectorParent = + newTemplate.addNode(AkbTypes.AKB_CONNECTOR_FOLDER, + AkbTypes.AKB_CONNECTOR_FOLDER); + // connectorParent.setProperty(Property.JCR_TITLE, connectorParentName); return newTemplate; } + // ////////////////////////// + // ENVIRONMENTS + @Override + public Node createActiveEnv(Node template, String name, + boolean copyDefaultConnectors) throws RepositoryException { + + Session session = template.getSession(); + Node parentEnvNode = session.getNode(AKB_ENVIRONMENTS_BASE_PATH); + Node createdEnv = parentEnvNode.addNode(name, AkbTypes.AKB_ENV); + createdEnv.setProperty(AKB_ENV_TEMPLATE_PATH, template.getPath()); + createdEnv.setProperty(Property.JCR_TITLE, name); + + Node connectorParent = createdEnv.addNode( + AkbTypes.AKB_CONNECTOR_FOLDER, AkbTypes.AKB_CONNECTOR_FOLDER); + + NodeIterator ni = template.getNode(AkbTypes.AKB_CONNECTOR_FOLDER) + .getNodes(); + while (ni.hasNext()) { + Node currNode = ni.nextNode(); + if (currNode.isNodeType(AkbTypes.AKB_CONNECTOR_ALIAS)) { + Node newConnector = connectorParent.addNode(currNode.getName(), + AkbTypes.AKB_CONNECTOR); + newConnector.setProperty(AKB_CONNECTOR_ALIAS_PATH, + currNode.getPath()); + if (copyDefaultConnectors + && currNode + .hasNode(AkbNames.AKB_DEFAULT_TEST_CONNECTOR)) { + Node defaultConn = currNode + .getNode(AkbNames.AKB_DEFAULT_TEST_CONNECTOR); + if (defaultConn.hasProperty(AkbNames.AKB_CONNECTOR_URL)) + newConnector + .setProperty( + AkbNames.AKB_CONNECTOR_URL, + defaultConn.getProperty( + AkbNames.AKB_CONNECTOR_URL) + .getString()); + if (defaultConn.hasProperty(AkbNames.AKB_CONNECTOR_USER)) + newConnector.setProperty( + AkbNames.AKB_CONNECTOR_USER, + defaultConn.getProperty( + AkbNames.AKB_CONNECTOR_USER) + .getString()); + } + } + } + return createdEnv; + } + // /////////////////////////////////////// - // / CONNECTORS + // CONNECTORS + + @Override + public Node createConnectorAlias(Node templateNode, String name, + String connectorType) throws RepositoryException { + Node parent = JcrUtils.mkdirs(templateNode, + AkbTypes.AKB_CONNECTOR_FOLDER, AkbTypes.AKB_CONNECTOR_FOLDER); + Node newConnector = parent.addNode(name, AkbTypes.AKB_CONNECTOR_ALIAS); + newConnector.setProperty(Property.JCR_TITLE, name); + newConnector.setProperty(AkbNames.AKB_CONNECTOR_TYPE, connectorType); + + // Node defaultConnector = + Node defaultConn = newConnector.addNode( + AkbNames.AKB_DEFAULT_TEST_CONNECTOR, connectorType); + defaultConn.setProperty(AkbNames.AKB_CONNECTOR_ALIAS_PATH, + newConnector.getPath()); + return newConnector; + } + + @Override + public NodeIterator getDefinedAliases(Node itemTemplate, + String connectorType) throws RepositoryException { + try { + Session session = itemTemplate.getSession(); + QueryManager queryManager = session.getWorkspace() + .getQueryManager(); + QueryObjectModelFactory factory = queryManager.getQOMFactory(); + + Selector source = factory.selector(AkbTypes.AKB_CONNECTOR_ALIAS, + AkbTypes.AKB_CONNECTOR_ALIAS); + Constraint defaultC = factory.descendantNode( + source.getSelectorName(), itemTemplate.getPath()); + + if (connectorType != null) { + Constraint connType = factory.comparison(factory.propertyValue( + source.getSelectorName(), AkbNames.AKB_CONNECTOR_TYPE), + QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, + factory.literal(session.getValueFactory().createValue( + connectorType))); + defaultC = factory.and(defaultC, connType); + } + + // Order by default by JCR TITLE + // TODO check if node definition has MIX_TITLE mixin + // TODO Apparently case insensitive ordering is not implemented in + // current used JCR implementation + Ordering order = factory + .ascending(factory.upperCase(factory.propertyValue( + source.getSelectorName(), Property.JCR_TITLE))); + QueryObjectModel query; + query = factory.createQuery(source, defaultC, + new Ordering[] { order }, null); + QueryResult result = query.execute(); + return result.getNodes(); + } catch (RepositoryException e) { + throw new AkbException("Unable to list connector", e); + } + } + + @Override + public Node getActiveConnectorByAlias(Node envNode, String aliasPath) + throws RepositoryException { + try { + Session session = envNode.getSession(); + QueryManager queryManager = session.getWorkspace() + .getQueryManager(); + QueryObjectModelFactory factory = queryManager.getQOMFactory(); + + Selector source = factory.selector(AkbTypes.AKB_CONNECTOR, + AkbTypes.AKB_CONNECTOR); + Constraint defaultC = factory.descendantNode( + source.getSelectorName(), envNode.getPath()); + + Constraint connType = factory.comparison( + factory.propertyValue(source.getSelectorName(), + AkbNames.AKB_CONNECTOR_ALIAS_PATH), + QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, factory + .literal(session.getValueFactory().createValue( + aliasPath))); + defaultC = factory.and(defaultC, connType); + + QueryObjectModel query; + query = factory.createQuery(source, defaultC, null, null); + QueryResult result = query.execute(); + NodeIterator ni = result.getNodes(); + + if (!ni.hasNext()) + return null; + else { + Node connector = ni.nextNode(); + if (ni.hasNext()) + throw new AkbException("More than one alias with name " + + aliasPath + " has been defined for environment " + + envNode); + else + return connector; + } + } catch (RepositoryException e) { + throw new AkbException("Unable to get connector " + aliasPath + + " in " + envNode, e); + } + } + + @Override + public boolean testConnector(Node connectorNode) { + try { + if (connectorNode.isNodeType(AkbTypes.AKB_JDBC_CONNECTOR)) { + String connectorUrl = connectorNode.getProperty( + AKB_CONNECTOR_URL).getString(); + String connectorUser = connectorNode.getProperty( + AKB_CONNECTOR_USER).getString(); + + String pwdPath = getPasswordPath(connectorNode); + char[] pwd = keyring.getAsChars(pwdPath); + DriverManager.getConnection(connectorUrl, connectorUser, + new String(pwd)); + savePassword(connectorNode.getSession(), pwdPath, pwd); + return true; + } else if (connectorNode.isNodeType(AkbTypes.AKB_SSH_CONNECTOR)) { + String connectorUrl = connectorNode.getProperty( + AKB_CONNECTOR_URL).getString(); + String connectorUser = connectorNode.getProperty( + AKB_CONNECTOR_USER).getString(); + String pwdPath = getPasswordPath(connectorNode); + char[] pwd = keyring.getAsChars(pwdPath); + + URI url = new URI(connectorUrl); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) + port = 22; + JSch jsch = new JSch(); + com.jcraft.jsch.Session sess = jsch.getSession(connectorUser, + host, port); + SimpleUserInfo userInfo = new SimpleUserInfo(); + userInfo.setPassword(new String(pwd)); + sess.setUserInfo(userInfo); + sess.connect(); + sess.disconnect(); + + savePassword(connectorNode.getSession(), pwdPath, pwd); + return true; + } else { + throw new SlcException("Unsupported connector " + connectorNode); + } + } catch (Exception e) { + throw new SlcException("Cannot test connection", e); + } + } + + /** + * Opens a new connection each time. All resources must be cleaned by + * caller. + */ + public PreparedStatement prepareJdbcQuery(Node activeEnv, Node node) { + PreparedStatement statement = null; + try { + + if (node.isNodeType(AkbTypes.AKB_JDBC_QUERY)) { + String connectorAliasStr = node.getProperty(AKB_USED_CONNECTOR) + .getString(); + // in case of a template passed env can be null + if (activeEnv == null) { + activeEnv = AkbJcrUtils.getCurrentTemplate(node); + } + Node connectorNode = getActiveConnectorByAlias(activeEnv, + connectorAliasStr); + + String sqlQuery = node.getProperty(AKB_QUERY_TEXT).getString(); + + String connectorUrl = AkbJcrUtils.get(connectorNode, + AKB_CONNECTOR_URL); + String connectorUser = AkbJcrUtils.get(connectorNode, + AKB_CONNECTOR_USER); + + // Sanity check + if (AkbJcrUtils.isEmptyString(connectorUrl) + || AkbJcrUtils.isEmptyString(connectorUser)) + return null; + + String pwdPath = getPasswordPath(connectorNode); + // String pwdPath = connectorNode.getPath() + '/' + // + ArgeoNames.ARGEO_PASSWORD; + char[] pwd = keyring.getAsChars(pwdPath); + Connection connection = DriverManager.getConnection( + connectorUrl, connectorUser, new String(pwd)); + try { + statement = connection.prepareStatement(sqlQuery, + ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_READ_ONLY); + } catch (SQLFeatureNotSupportedException e) { + log.warn("Scroll not supported for " + connectorUrl); + statement = connection.prepareStatement(sqlQuery); + } + } else { + throw new SlcException("Unsupported node " + node); + } + return statement; + } catch (Exception e) { + throw new SlcException("Cannot execute test JDBC query on " + node, + e); + } + } + + public String executeCommand(Node activeEnv, Node node) { + try { + + String connectorAliasStr = node.getProperty(AKB_USED_CONNECTOR) + .getString(); + // in case of a template passed env can be null + if (activeEnv == null) { + activeEnv = AkbJcrUtils.getCurrentTemplate(node); + } + Node connectorNode = getActiveConnectorByAlias(activeEnv, + connectorAliasStr); + String command = node.getProperty(AkbNames.AKB_COMMAND_TEXT) + .getString(); + + String connectorUrl = AkbJcrUtils.get(connectorNode, + AKB_CONNECTOR_URL); + String connectorUser = AkbJcrUtils.get(connectorNode, + AKB_CONNECTOR_USER); + + // Sanity check + if (AkbJcrUtils.isEmptyString(connectorUrl) + || AkbJcrUtils.isEmptyString(connectorUser)) + return null; + + String pwdPath = getPasswordPath(connectorNode); + char[] pwd = keyring.getAsChars(pwdPath); + + URI url = new URI(connectorUrl); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) + port = 22; + JSch jsch = new JSch(); + com.jcraft.jsch.Session sess = jsch.getSession(connectorUser, host, + port); + SimpleUserInfo userInfo = new SimpleUserInfo(); + userInfo.setPassword(new String(pwd)); + sess.setUserInfo(userInfo); + sess.connect(); + + sess.openChannel("exec"); + final ChannelExec channel = (ChannelExec) sess.openChannel("exec"); + channel.setCommand(command); + + channel.setInputStream(null); + channel.setXForwarding(false); + channel.setAgentForwarding(false); + channel.setErrStream(null); + + channel.connect(); + + String output = IOUtils.toString(channel.getInputStream()); + channel.disconnect(); + + sess.disconnect(); + + return output; + } catch (Exception e) { + throw new SlcException("Cannot execute command", e); + } + + } + + public String retrieveFile(Node activeEnv, Node node) { + try { + String filePath = node.getProperty(AkbNames.AKB_FILE_PATH) + .getString(); + String command = "cat " + filePath; + + String connectorAliasStr = node.getProperty(AKB_USED_CONNECTOR) + .getString(); + // in case of a template passed env can be null + if (activeEnv == null) { + activeEnv = AkbJcrUtils.getCurrentTemplate(node); + } + Node connectorNode = getActiveConnectorByAlias(activeEnv, + connectorAliasStr); + + // TODO do a proper scp + String connectorUrl = AkbJcrUtils.get(connectorNode, + AKB_CONNECTOR_URL); + String connectorUser = AkbJcrUtils.get(connectorNode, + AKB_CONNECTOR_USER); + + // Sanity check + if (AkbJcrUtils.isEmptyString(connectorUrl) + || AkbJcrUtils.isEmptyString(connectorUser)) + return null; + + String pwdPath = getPasswordPath(connectorNode); + char[] pwd = keyring.getAsChars(pwdPath); + + URI url = new URI(connectorUrl); + String host = url.getHost(); + int port = url.getPort(); + if (port == -1) + port = 22; + JSch jsch = new JSch(); + com.jcraft.jsch.Session sess = jsch.getSession(connectorUser, host, + port); + SimpleUserInfo userInfo = new SimpleUserInfo(); + userInfo.setPassword(new String(pwd)); + sess.setUserInfo(userInfo); + sess.connect(); + + sess.openChannel("exec"); + final ChannelExec channel = (ChannelExec) sess.openChannel("exec"); + channel.setCommand(command); + + channel.setInputStream(null); + channel.setXForwarding(false); + channel.setAgentForwarding(false); + channel.setErrStream(null); + + channel.connect(); + + String output = IOUtils.toString(channel.getInputStream()); + channel.disconnect(); + + sess.disconnect(); + + return output; + } catch (Exception e) { + throw new SlcException("Cannot execute command", e); + } + + } + + protected String getPasswordPath(Node node) throws RepositoryException { + Node home = UserJcrUtils.getUserHome(node.getSession()); + if (node.getPath().startsWith(home.getPath())) + return node.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; + else + return home.getPath() + node.getPath() + '/' + + ArgeoNames.ARGEO_PASSWORD; + } + + private void savePassword(Session session, String pwdPath, char[] pwd) + throws RepositoryException { + if (!session.itemExists(pwdPath)) { + JcrUtils.mkdirs(session, JcrUtils.parentPath(pwdPath)); + session.save(); + keyring.set(pwdPath, pwd); + } - public boolean testConnector(Node connector) { - // TODO Implement this. - return false; } - // /** Expose injected repository */ - // public Repository getRepository() { - // return repository; - // } + /** Expose injected repository */ + public Repository getRepository() { + return repository; + } /* DEPENDENCY INJECTION */ public void setRepository(Repository repository) { @@ -107,4 +536,9 @@ public class AkbServiceImpl implements AkbService, AkbNames { public void setDemoData(Map demoData) { this.demoData = demoData; } + + public void setKeyring(Keyring keyring) { + this.keyring = keyring; + } + } \ No newline at end of file