]> git.argeo.org Git - gpl/argeo-slc.git/blob - runtime/org.argeo.slc.akb/src/main/java/org/argeo/slc/akb/core/AkbServiceImpl.java
Edit connector wizard
[gpl/argeo-slc.git] / runtime / org.argeo.slc.akb / src / main / java / org / argeo / slc / akb / core / AkbServiceImpl.java
1 package org.argeo.slc.akb.core;
2
3 import java.net.URI;
4 import java.sql.Connection;
5 import java.sql.DriverManager;
6 import java.sql.PreparedStatement;
7 import java.sql.ResultSet;
8 import java.sql.SQLFeatureNotSupportedException;
9 import java.util.Map;
10
11 import javax.annotation.Resource;
12 import javax.jcr.Node;
13 import javax.jcr.NodeIterator;
14 import javax.jcr.Property;
15 import javax.jcr.Repository;
16 import javax.jcr.RepositoryException;
17 import javax.jcr.Session;
18 import javax.jcr.query.QueryManager;
19 import javax.jcr.query.QueryResult;
20 import javax.jcr.query.qom.Constraint;
21 import javax.jcr.query.qom.Ordering;
22 import javax.jcr.query.qom.QueryObjectModel;
23 import javax.jcr.query.qom.QueryObjectModelConstants;
24 import javax.jcr.query.qom.QueryObjectModelFactory;
25 import javax.jcr.query.qom.Selector;
26
27 import org.apache.commons.io.IOUtils;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.argeo.jcr.ArgeoNames;
31 import org.argeo.jcr.JcrUtils;
32 import org.argeo.jcr.UserJcrUtils;
33 import org.argeo.slc.SlcException;
34 import org.argeo.slc.akb.AkbException;
35 import org.argeo.slc.akb.AkbNames;
36 import org.argeo.slc.akb.AkbService;
37 import org.argeo.slc.akb.AkbTypes;
38 import org.argeo.slc.akb.utils.AkbJcrUtils;
39 import org.argeo.slc.jsch.SimpleUserInfo;
40 import org.argeo.util.security.Keyring;
41
42 import com.jcraft.jsch.ChannelExec;
43 import com.jcraft.jsch.JSch;
44
45 /**
46 * Concrete access to akb services. It provides among other an initialized
47 * environment
48 */
49 public class AkbServiceImpl implements AkbService, AkbNames {
50 private final static Log log = LogFactory.getLog(AkbServiceImpl.class);
51
52 /* DEPENDENCY INJECTION */
53 private Repository repository;
54
55 private Keyring keyring;
56
57 // Populate the repository in a demo context.
58 private Map<String, Resource> demoData = null;
59
60 /* Life cycle management */
61 /**
62 * Call by each startup in order to make sure the backend is ready to
63 * receive/provide data.
64 */
65 public void init() {
66 // JDBC drivers
67 // TODO make it configurable
68 initJdbcDriver("org.postgresql.Driver");
69
70 Session adminSession = null;
71 try {
72 adminSession = repository.login();
73
74 // Initialization of the model
75 if (!adminSession.nodeExists(AKB_TEMPLATES_BASE_PATH)) {
76 JcrUtils.mkdirs(adminSession, AKB_TEMPLATES_BASE_PATH);
77 JcrUtils.mkdirs(adminSession, AKB_ENVIRONMENTS_BASE_PATH);
78 adminSession.save();
79 log.info("Repository has been initialized "
80 + "with AKB's model");
81 }
82
83 // Fill the repository in a demo context
84 if (demoData != null) {
85 // Dev only force reload at each start
86 // if (true) {
87 // if (!projectsPar.hasNodes()) {
88 }
89 } catch (Exception e) {
90 throw new AkbException("Cannot initialize backend", e);
91 } finally {
92 JcrUtils.logoutQuietly(adminSession);
93 }
94 }
95
96 protected Boolean initJdbcDriver(String driver) {
97 try {
98 Class.forName(driver);
99 return true;
100 } catch (ClassNotFoundException e) {
101 if (log.isDebugEnabled())
102 log.debug("Cannot load JDBC driver : " + driver + ", "
103 + e.getMessage());
104 return false;
105 }
106 }
107
108 /** Clean shutdown of the backend. */
109 public void destroy() {
110 }
111
112 @Override
113 public Node createAkbTemplate(Node parentNode, String name)
114 throws RepositoryException {
115 Node newTemplate = parentNode.addNode(name, AkbTypes.AKB_ENV_TEMPLATE);
116 newTemplate.setProperty(Property.JCR_TITLE, name);
117 newTemplate.addNode(AkbTypes.AKB_CONNECTOR_FOLDER,
118 AkbTypes.AKB_CONNECTOR_FOLDER);
119 return newTemplate;
120 }
121
122 // //////////////////////////
123 // ENVIRONMENTS
124 @Override
125 public Node createActiveEnv(Node template, String name,
126 boolean copyDefaultConnectors) throws RepositoryException {
127
128 Session session = template.getSession();
129 Node parentEnvNode = session.getNode(AKB_ENVIRONMENTS_BASE_PATH);
130 Node createdEnv = parentEnvNode.addNode(name, AkbTypes.AKB_ENV);
131 createdEnv.setProperty(AKB_ENV_TEMPLATE_PATH, template.getPath());
132 createdEnv.setProperty(Property.JCR_TITLE, name);
133
134 Node connectorParent = createdEnv.addNode(
135 AkbTypes.AKB_CONNECTOR_FOLDER, AkbTypes.AKB_CONNECTOR_FOLDER);
136
137 NodeIterator ni = template.getNode(AkbTypes.AKB_CONNECTOR_FOLDER)
138 .getNodes();
139 activeConns: while (ni.hasNext()) {
140 Node currNode = ni.nextNode();
141 if (currNode.isNodeType(AkbTypes.AKB_CONNECTOR_ALIAS)) {
142 String connType = currNode.getProperty(AKB_CONNECTOR_TYPE)
143 .getString();
144
145 if (AkbJcrUtils.isEmptyString(connType))
146 // Cannot create an instance if the type is undefined
147 continue activeConns;
148
149 Node newConnector = connectorParent.addNode(currNode.getName(),
150 connType);
151 newConnector.setProperty(AKB_CONNECTOR_ALIAS_PATH,
152 currNode.getPath());
153 if (copyDefaultConnectors
154 && currNode
155 .hasNode(AkbNames.AKB_DEFAULT_TEST_CONNECTOR)) {
156 Node defaultConn = currNode
157 .getNode(AkbNames.AKB_DEFAULT_TEST_CONNECTOR);
158 if (defaultConn.hasProperty(AkbNames.AKB_CONNECTOR_URL))
159 newConnector
160 .setProperty(
161 AkbNames.AKB_CONNECTOR_URL,
162 defaultConn.getProperty(
163 AkbNames.AKB_CONNECTOR_URL)
164 .getString());
165 if (defaultConn.hasProperty(AkbNames.AKB_CONNECTOR_USER))
166 newConnector.setProperty(
167 AkbNames.AKB_CONNECTOR_USER,
168 defaultConn.getProperty(
169 AkbNames.AKB_CONNECTOR_USER)
170 .getString());
171 }
172 }
173 }
174 return createdEnv;
175 }
176
177 // ///////////////////////////////////////
178 // CONNECTORS
179
180 @Override
181 public Node createConnectorAlias(Node templateNode, String name,
182 String connectorType) throws RepositoryException {
183 Node parent = JcrUtils.mkdirs(templateNode,
184 AkbTypes.AKB_CONNECTOR_FOLDER, AkbTypes.AKB_CONNECTOR_FOLDER);
185 Node newConnector = parent.addNode(name, AkbTypes.AKB_CONNECTOR_ALIAS);
186 newConnector.setProperty(Property.JCR_TITLE, name);
187 newConnector.setProperty(AkbNames.AKB_CONNECTOR_TYPE, connectorType);
188
189 // Node defaultConnector =
190 Node defaultConn = newConnector.addNode(
191 AkbNames.AKB_DEFAULT_TEST_CONNECTOR, connectorType);
192 defaultConn.setProperty(AkbNames.AKB_CONNECTOR_ALIAS_PATH,
193 newConnector.getPath());
194 return newConnector;
195 }
196
197 @Override
198 public NodeIterator getDefinedAliases(Node itemTemplate,
199 String connectorType) throws RepositoryException {
200 try {
201 Session session = itemTemplate.getSession();
202 QueryManager queryManager = session.getWorkspace()
203 .getQueryManager();
204 QueryObjectModelFactory factory = queryManager.getQOMFactory();
205
206 Selector source = factory.selector(AkbTypes.AKB_CONNECTOR_ALIAS,
207 AkbTypes.AKB_CONNECTOR_ALIAS);
208 Constraint defaultC = factory.descendantNode(
209 source.getSelectorName(), itemTemplate.getPath());
210
211 if (connectorType != null) {
212 Constraint connType = factory.comparison(factory.propertyValue(
213 source.getSelectorName(), AkbNames.AKB_CONNECTOR_TYPE),
214 QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO,
215 factory.literal(session.getValueFactory().createValue(
216 connectorType)));
217 defaultC = factory.and(defaultC, connType);
218 }
219
220 // Order by default by JCR TITLE
221 // TODO check if node definition has MIX_TITLE mixin
222 // TODO Apparently case insensitive ordering is not implemented in
223 // current used JCR implementation
224 Ordering order = factory
225 .ascending(factory.upperCase(factory.propertyValue(
226 source.getSelectorName(), Property.JCR_TITLE)));
227 QueryObjectModel query;
228 query = factory.createQuery(source, defaultC,
229 new Ordering[] { order }, null);
230 QueryResult result = query.execute();
231 return result.getNodes();
232 } catch (RepositoryException e) {
233 throw new AkbException("Unable to list connector", e);
234 }
235 }
236
237 @Override
238 public Node getActiveConnectorByAlias(Node envNode, String aliasPath)
239 throws RepositoryException {
240 try {
241 Session session = envNode.getSession();
242 QueryManager queryManager = session.getWorkspace()
243 .getQueryManager();
244 QueryObjectModelFactory factory = queryManager.getQOMFactory();
245
246 Selector source = factory.selector(AkbTypes.AKB_CONNECTOR,
247 AkbTypes.AKB_CONNECTOR);
248 Constraint defaultC = factory.descendantNode(
249 source.getSelectorName(), envNode.getPath());
250
251 Constraint connType = factory.comparison(
252 factory.propertyValue(source.getSelectorName(),
253 AkbNames.AKB_CONNECTOR_ALIAS_PATH),
254 QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, factory
255 .literal(session.getValueFactory().createValue(
256 aliasPath)));
257 defaultC = factory.and(defaultC, connType);
258
259 QueryObjectModel query;
260 query = factory.createQuery(source, defaultC, null, null);
261 QueryResult result = query.execute();
262 NodeIterator ni = result.getNodes();
263
264 if (!ni.hasNext())
265 return null;
266 else {
267 Node connector = ni.nextNode();
268 if (ni.hasNext())
269 throw new AkbException("More than one alias with name "
270 + aliasPath + " has been defined for environment "
271 + envNode);
272 else
273 return connector;
274 }
275 } catch (RepositoryException e) {
276 throw new AkbException("Unable to get connector " + aliasPath
277 + " in " + envNode, e);
278 }
279 }
280
281 @Override
282 public boolean testConnector(Node connectorNode) {
283 try {
284 if (connectorNode.isNodeType(AkbTypes.AKB_JDBC_CONNECTOR)) {
285 String connectorUrl = connectorNode.getProperty(
286 AKB_CONNECTOR_URL).getString();
287 String connectorUser = connectorNode.getProperty(
288 AKB_CONNECTOR_USER).getString();
289
290 String pwdPath = getPasswordPath(connectorNode);
291 char[] pwd = keyring.getAsChars(pwdPath);
292 DriverManager.getConnection(connectorUrl, connectorUser,
293 new String(pwd));
294 savePassword(connectorNode.getSession(), pwdPath, pwd);
295 return true;
296 } else if (connectorNode.isNodeType(AkbTypes.AKB_SSH_CONNECTOR)) {
297 String connectorUrl = connectorNode.getProperty(
298 AKB_CONNECTOR_URL).getString();
299 String connectorUser = connectorNode.getProperty(
300 AKB_CONNECTOR_USER).getString();
301 String pwdPath = getPasswordPath(connectorNode);
302 char[] pwd = keyring.getAsChars(pwdPath);
303
304 URI url = new URI(connectorUrl);
305 String host = url.getHost();
306 int port = url.getPort();
307 if (port == -1)
308 port = 22;
309 JSch jsch = new JSch();
310 com.jcraft.jsch.Session sess = jsch.getSession(connectorUser,
311 host, port);
312 SimpleUserInfo userInfo = new SimpleUserInfo();
313 userInfo.setPassword(new String(pwd));
314 sess.setUserInfo(userInfo);
315 sess.connect();
316 sess.disconnect();
317
318 savePassword(connectorNode.getSession(), pwdPath, pwd);
319 return true;
320 } else {
321 throw new SlcException("Unsupported connector " + connectorNode);
322 }
323 } catch (Exception e) {
324 throw new SlcException("Cannot test connection", e);
325 }
326 }
327
328 /**
329 * Opens a new connection each time. All resources must be cleaned by
330 * caller.
331 */
332 public PreparedStatement prepareJdbcQuery(Node activeEnv, Node node) {
333 PreparedStatement statement = null;
334 try {
335
336 if (node.isNodeType(AkbTypes.AKB_JDBC_QUERY)) {
337 String connectorAliasStr = node.getProperty(AKB_USED_CONNECTOR)
338 .getString();
339 // in case of a template passed env can be null
340 if (activeEnv == null) {
341 activeEnv = AkbJcrUtils.getCurrentTemplate(node);
342 }
343 Node connectorNode = getActiveConnectorByAlias(activeEnv,
344 connectorAliasStr);
345
346 String sqlQuery = node.getProperty(AKB_QUERY_TEXT).getString();
347
348 String connectorUrl = AkbJcrUtils.get(connectorNode,
349 AKB_CONNECTOR_URL);
350 String connectorUser = AkbJcrUtils.get(connectorNode,
351 AKB_CONNECTOR_USER);
352
353 // Sanity check
354 if (AkbJcrUtils.isEmptyString(connectorUrl)
355 || AkbJcrUtils.isEmptyString(connectorUser))
356 return null;
357
358 String pwdPath = getPasswordPath(connectorNode);
359 // String pwdPath = connectorNode.getPath() + '/'
360 // + ArgeoNames.ARGEO_PASSWORD;
361 char[] pwd = keyring.getAsChars(pwdPath);
362 Connection connection = DriverManager.getConnection(
363 connectorUrl, connectorUser, new String(pwd));
364 try {
365 statement = connection.prepareStatement(sqlQuery,
366 ResultSet.TYPE_SCROLL_INSENSITIVE,
367 ResultSet.CONCUR_READ_ONLY);
368 } catch (SQLFeatureNotSupportedException e) {
369 log.warn("Scroll not supported for " + connectorUrl);
370 statement = connection.prepareStatement(sqlQuery);
371 }
372 } else {
373 throw new SlcException("Unsupported node " + node);
374 }
375 return statement;
376 } catch (Exception e) {
377 throw new SlcException("Cannot execute test JDBC query on " + node,
378 e);
379 }
380 }
381
382 public String executeCommand(Node activeEnv, Node node) {
383 try {
384
385 String connectorAliasStr = node.getProperty(AKB_USED_CONNECTOR)
386 .getString();
387 // in case of a template passed env can be null
388 if (activeEnv == null) {
389 activeEnv = AkbJcrUtils.getCurrentTemplate(node);
390 }
391 Node connectorNode = getActiveConnectorByAlias(activeEnv,
392 connectorAliasStr);
393 String command = node.getProperty(AkbNames.AKB_COMMAND_TEXT)
394 .getString();
395
396 String connectorUrl = AkbJcrUtils.get(connectorNode,
397 AKB_CONNECTOR_URL);
398 String connectorUser = AkbJcrUtils.get(connectorNode,
399 AKB_CONNECTOR_USER);
400
401 // Sanity check
402 if (AkbJcrUtils.isEmptyString(connectorUrl)
403 || AkbJcrUtils.isEmptyString(connectorUser))
404 return null;
405
406 String pwdPath = getPasswordPath(connectorNode);
407 char[] pwd = keyring.getAsChars(pwdPath);
408
409 URI url = new URI(connectorUrl);
410 String host = url.getHost();
411 int port = url.getPort();
412 if (port == -1)
413 port = 22;
414 JSch jsch = new JSch();
415 com.jcraft.jsch.Session sess = jsch.getSession(connectorUser, host,
416 port);
417 SimpleUserInfo userInfo = new SimpleUserInfo();
418 userInfo.setPassword(new String(pwd));
419 sess.setUserInfo(userInfo);
420 sess.connect();
421
422 sess.openChannel("exec");
423 final ChannelExec channel = (ChannelExec) sess.openChannel("exec");
424 channel.setCommand(command);
425
426 channel.setInputStream(null);
427 channel.setXForwarding(false);
428 channel.setAgentForwarding(false);
429 channel.setErrStream(null);
430
431 channel.connect();
432
433 String output = IOUtils.toString(channel.getInputStream());
434 channel.disconnect();
435
436 sess.disconnect();
437
438 return output;
439 } catch (Exception e) {
440 throw new SlcException("Cannot execute command", e);
441 }
442
443 }
444
445 public String retrieveFile(Node activeEnv, Node node) {
446 try {
447 String filePath = node.getProperty(AkbNames.AKB_FILE_PATH)
448 .getString();
449 String command = "cat " + filePath;
450
451 String connectorAliasStr = node.getProperty(AKB_USED_CONNECTOR)
452 .getString();
453 // in case of a template passed env can be null
454 if (activeEnv == null) {
455 activeEnv = AkbJcrUtils.getCurrentTemplate(node);
456 }
457 Node connectorNode = getActiveConnectorByAlias(activeEnv,
458 connectorAliasStr);
459
460 // TODO do a proper scp
461 String connectorUrl = AkbJcrUtils.get(connectorNode,
462 AKB_CONNECTOR_URL);
463 String connectorUser = AkbJcrUtils.get(connectorNode,
464 AKB_CONNECTOR_USER);
465
466 // Sanity check
467 if (AkbJcrUtils.isEmptyString(connectorUrl)
468 || AkbJcrUtils.isEmptyString(connectorUser))
469 return null;
470
471 String pwdPath = getPasswordPath(connectorNode);
472 char[] pwd = keyring.getAsChars(pwdPath);
473
474 URI url = new URI(connectorUrl);
475 String host = url.getHost();
476 int port = url.getPort();
477 if (port == -1)
478 port = 22;
479 JSch jsch = new JSch();
480 com.jcraft.jsch.Session sess = jsch.getSession(connectorUser, host,
481 port);
482 SimpleUserInfo userInfo = new SimpleUserInfo();
483 userInfo.setPassword(new String(pwd));
484 sess.setUserInfo(userInfo);
485 sess.connect();
486
487 sess.openChannel("exec");
488 final ChannelExec channel = (ChannelExec) sess.openChannel("exec");
489 channel.setCommand(command);
490
491 channel.setInputStream(null);
492 channel.setXForwarding(false);
493 channel.setAgentForwarding(false);
494 channel.setErrStream(null);
495
496 channel.connect();
497
498 String output = IOUtils.toString(channel.getInputStream());
499 channel.disconnect();
500
501 sess.disconnect();
502
503 return output;
504 } catch (Exception e) {
505 throw new SlcException("Cannot execute command", e);
506 }
507
508 }
509
510 protected String getPasswordPath(Node node) throws RepositoryException {
511 Node home = UserJcrUtils.getUserHome(node.getSession());
512 if (node.getPath().startsWith(home.getPath()))
513 return node.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD;
514 else
515 return home.getPath() + node.getPath() + '/'
516 + ArgeoNames.ARGEO_PASSWORD;
517 }
518
519 private void savePassword(Session session, String pwdPath, char[] pwd)
520 throws RepositoryException {
521 if (!session.itemExists(pwdPath)) {
522 JcrUtils.mkdirs(session, JcrUtils.parentPath(pwdPath));
523 session.save();
524 keyring.set(pwdPath, pwd);
525 }
526
527 }
528
529 /** Expose injected repository */
530 public Repository getRepository() {
531 return repository;
532 }
533
534 /* DEPENDENCY INJECTION */
535 public void setRepository(Repository repository) {
536 this.repository = repository;
537 }
538
539 public void setDemoData(Map<String, Resource> demoData) {
540 this.demoData = demoData;
541 }
542
543 public void setKeyring(Keyring keyring) {
544 this.keyring = keyring;
545 }
546
547 }