Convert sql-resource provider to blueprint
[ccsdk/sli/adaptors.git] / sql-resource / provider / src / main / java / org / onap / ccsdk / sli / adaptors / resource / sql / SqlResource.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openECOMP : SDN-C
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights
6  *             reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.ccsdk.sli.adaptors.resource.sql;
23
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.sql.Connection;
27 import java.sql.PreparedStatement;
28 import java.sql.ResultSet;
29 import java.sql.ResultSetMetaData;
30 import java.sql.SQLException;
31 import java.util.ArrayList;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.Properties;
35
36 import javax.sql.rowset.CachedRowSet;
37
38 import org.apache.commons.lang3.StringUtils;
39 import org.onap.ccsdk.sli.core.dblib.DBResourceManager;
40 import org.onap.ccsdk.sli.core.dblib.DbLibService;
41 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
42 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
43 import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin;
44 import org.onap.ccsdk.sli.core.sli.SvcLogicResource;
45 import org.osgi.framework.Bundle;
46 import org.osgi.framework.BundleContext;
47 import org.osgi.framework.FrameworkUtil;
48 import org.osgi.framework.ServiceReference;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class SqlResource implements SvcLogicResource, SvcLogicJavaPlugin {
53
54     private static final Logger LOG = LoggerFactory.getLogger(SqlResource.class);
55
56     private static final String DBLIB_SERVICE = "org.onap.ccsdk.sli.core.dblib.DbLibService";
57
58     private static String CRYPT_KEY = "";
59
60     DbLibService dblibSvc = null;
61
62     public SqlResource(SqlResourcePropertiesProvider propProvider) {
63         this(propProvider, null);
64     }
65
66     public SqlResource(SqlResourcePropertiesProvider propProvider, DbLibService dblibSvc) {
67
68         this.dblibSvc = dblibSvc;
69
70         Properties properties = propProvider.getProperties();
71
72         String cryptKey = properties.getProperty("org.onap.sdnc.resource.sql.cryptkey");
73
74         if ((cryptKey == null) || (cryptKey.length() == 0)) {
75             cryptKey = properties.getProperty("org.openecomp.sdnc.resource.sql.cryptkey");
76         }
77
78         SqlResource.setCryptKey(cryptKey);
79     }
80
81     // For sql-resource, is-available is the same as exists
82     @Override
83     public QueryStatus isAvailable(String resource, String key, String prefix, SvcLogicContext ctx)
84             throws SvcLogicException {
85
86         return (exists(resource, key, prefix, ctx));
87
88     }
89
90     @Override
91     public QueryStatus exists(String resource, String key, String prefix, SvcLogicContext ctx)
92             throws SvcLogicException {
93
94         DbLibService dblibSvc = getDbLibService();
95         if (dblibSvc == null) {
96             return (QueryStatus.FAILURE);
97         }
98
99         String theStmt = resolveCtxVars(key, ctx);
100
101         try {
102             CachedRowSet results = dblibSvc.getData(theStmt, null, null);
103
104             if (!results.next()) {
105                 return (QueryStatus.NOT_FOUND);
106             }
107
108             int numRows = results.getInt(1);
109
110             if (numRows > 0) {
111                 return (QueryStatus.SUCCESS);
112             } else {
113                 return (QueryStatus.NOT_FOUND);
114             }
115         } catch (Exception e) {
116             LOG.error("Caught SQL exception", e);
117             return (QueryStatus.FAILURE);
118         }
119     }
120
121     // @Override
122     public QueryStatus query(String resource, boolean localOnly, String select, String key, String prefix,
123             String orderBy, SvcLogicContext ctx) throws SvcLogicException {
124
125         DbLibService dblibSvc = getDbLibService();
126
127         if (dblibSvc == null) {
128             return (QueryStatus.FAILURE);
129         }
130
131         String sqlQuery = resolveCtxVars(key, ctx);
132
133         try {
134
135             CachedRowSet results = dblibSvc.getData(sqlQuery, null, null);
136
137             QueryStatus retval = QueryStatus.SUCCESS;
138
139             if (!results.next()) {
140                 retval = QueryStatus.NOT_FOUND;
141                 LOG.debug("No data found");
142             } else {
143                 saveCachedRowSetToCtx(results, ctx, prefix, dblibSvc);
144             }
145             return (retval);
146         } catch (Exception e) {
147             LOG.error("Caught SQL exception", e);
148             return (QueryStatus.FAILURE);
149         }
150     }
151
152     public void saveCachedRowSetToCtx(CachedRowSet results, SvcLogicContext ctx, String prefix, DbLibService dblibSvc)
153             throws SQLException {
154         if (ctx != null) {
155             if ((prefix != null) && prefix.endsWith("[]")) {
156                 // Return an array.
157                 String pfx = prefix.substring(0, prefix.length() - 2);
158                 int idx = 0;
159                 do {
160                     ResultSetMetaData rsMeta = results.getMetaData();
161                     int numCols = rsMeta.getColumnCount();
162
163                     for (int i = 0; i < numCols; i++) {
164                         String colValue = null;
165                         String tableName = rsMeta.getTableName(i + 1);
166                         if (rsMeta.getColumnType(i + 1) == java.sql.Types.VARBINARY) {
167                             colValue = decryptColumn(tableName, rsMeta.getColumnName(i + 1), results.getBytes(i + 1),
168                                     dblibSvc);
169                         } else {
170                             colValue = results.getString(i + 1);
171                         }
172                         LOG.debug("Setting " + pfx + "[" + idx + "]."
173                                 + rsMeta.getColumnLabel(i + 1).replaceAll("_", "-") + " = " + colValue);
174                         ctx.setAttribute(pfx + "[" + idx + "]." + rsMeta.getColumnLabel(i + 1).replaceAll("_", "-"),
175                                 colValue);
176                     }
177                     idx++;
178                 } while (results.next());
179                 LOG.debug("Setting " + pfx + "_length = " + idx);
180                 ctx.setAttribute(pfx + "_length", "" + idx);
181             } else {
182                 ResultSetMetaData rsMeta = results.getMetaData();
183                 int numCols = rsMeta.getColumnCount();
184
185                 for (int i = 0; i < numCols; i++) {
186                     String colValue = null;
187                     String tableName = rsMeta.getTableName(i + 1);
188                     if ("VARBINARY".equalsIgnoreCase(rsMeta.getColumnTypeName(i + 1))) {
189                         colValue = decryptColumn(tableName, rsMeta.getColumnName(i + 1), results.getBytes(i + 1),
190                                 dblibSvc);
191                     } else {
192                         colValue = results.getString(i + 1);
193                     }
194                     if (prefix != null) {
195                         LOG.debug("Setting " + prefix + "." + rsMeta.getColumnLabel(i + 1).replaceAll("_", "-") + " = "
196                                 + colValue);
197                         ctx.setAttribute(prefix + "." + rsMeta.getColumnLabel(i + 1).replaceAll("_", "-"), colValue);
198                     } else {
199                         LOG.debug("Setting " + rsMeta.getColumnLabel(i + 1).replaceAll("_", "-") + " = " + colValue);
200                         ctx.setAttribute(rsMeta.getColumnLabel(i + 1).replaceAll("_", "-"), colValue);
201                     }
202                 }
203             }
204         }
205     }
206
207     // reserve is no-op
208     @Override
209     public QueryStatus reserve(String resource, String select, String key, String prefix, SvcLogicContext ctx)
210             throws SvcLogicException {
211         return (QueryStatus.SUCCESS);
212     }
213
214     // release is no-op
215     @Override
216     public QueryStatus release(String resource, String key, SvcLogicContext ctx) throws SvcLogicException {
217         return (QueryStatus.SUCCESS);
218     }
219
220     private QueryStatus executeSqlWrite(String key, SvcLogicContext ctx) throws SvcLogicException {
221         QueryStatus retval = QueryStatus.SUCCESS;
222
223         DbLibService dblibSvc = getDbLibService();
224
225         if (dblibSvc == null) {
226             return (QueryStatus.FAILURE);
227         }
228
229         String sqlStmt = resolveCtxVars(key, ctx);
230
231         LOG.debug("key = [" + key + "]; sqlStmt = [" + sqlStmt + "]");
232         try {
233
234             if (!dblibSvc.writeData(sqlStmt, null, null)) {
235                 retval = QueryStatus.FAILURE;
236             }
237         } catch (Exception e) {
238             LOG.error("Caught SQL exception", e);
239             retval = QueryStatus.FAILURE;
240         }
241
242         return (retval);
243
244     }
245
246     private String resolveCtxVars(String key, SvcLogicContext ctx) {
247         if (key == null) {
248             return (null);
249         }
250
251         if (key.startsWith("'") && key.endsWith("'")) {
252             key = key.substring(1, key.length() - 1);
253             LOG.debug("Stripped outer single quotes - key is now [" + key + "]");
254         }
255
256         String[] keyTerms = key.split("\\s+");
257
258         StringBuffer sqlBuffer = new StringBuffer();
259
260         for (int i = 0; i < keyTerms.length; i++) {
261             sqlBuffer.append(resolveTerm(keyTerms[i], ctx));
262             sqlBuffer.append(" ");
263         }
264
265         return (sqlBuffer.toString());
266     }
267
268     private String resolveTerm(String term, SvcLogicContext ctx) {
269         if (term == null) {
270             return (null);
271         }
272
273         LOG.trace("resolveTerm: term is " + term);
274
275         if (term.startsWith("$") && (ctx != null)) {
276             // Resolve any index variables.
277             term = resolveCtxVariable(term.substring(1), ctx);
278             // Escape single quote
279             if (term != null) {
280                 term = term.replaceAll("'", "''");
281             }
282             return ("'" + term + "'");
283         } else {
284             return (term);
285         }
286
287     }
288
289     private String resolveCtxVariable(String ctxVarName, SvcLogicContext ctx) {
290
291         if (ctxVarName.indexOf('[') == -1) {
292             // Ctx variable contains no arrays
293             if ("CRYPT_KEY".equals(ctxVarName)) {
294                 // Handle crypt key as special case. If it's set as a context
295                 // variable, use it. Otherwise, use
296                 // configured crypt key.
297                 String cryptKey = ctx.getAttribute(ctxVarName);
298                 if ((cryptKey != null) && (cryptKey.length() > 0)) {
299                     return (cryptKey);
300                 } else {
301                     return (CRYPT_KEY);
302                 }
303             }
304             return (ctx.getAttribute(ctxVarName));
305         }
306
307         // Resolve any array references
308         StringBuffer sbuff = new StringBuffer();
309         String[] ctxVarParts = ctxVarName.split("\\[");
310         sbuff.append(ctxVarParts[0]);
311         for (int i = 1; i < ctxVarParts.length; i++) {
312             if (ctxVarParts[i].startsWith("$")) {
313                 int endBracketLoc = ctxVarParts[i].indexOf("]");
314                 if (endBracketLoc == -1) {
315                     // Missing end bracket ... give up parsing
316                     LOG.warn("Variable reference " + ctxVarName + " seems to be missing a ']'");
317                     return (ctx.getAttribute(ctxVarName));
318                 }
319
320                 String idxVarName = ctxVarParts[i].substring(1, endBracketLoc);
321                 String remainder = ctxVarParts[i].substring(endBracketLoc);
322
323                 sbuff.append("[");
324                 sbuff.append(ctx.getAttribute(idxVarName));
325                 sbuff.append(remainder);
326
327             } else {
328                 // Index is not a variable reference
329                 sbuff.append("[");
330                 sbuff.append(ctxVarParts[i]);
331             }
332         }
333
334         return (ctx.getAttribute(sbuff.toString()));
335     }
336
337     @Override
338     public QueryStatus save(String resource, boolean force, boolean localOnly, String key, Map<String, String> parms,
339             String prefix, SvcLogicContext ctx) throws SvcLogicException {
340         return (executeSqlWrite(key, ctx));
341     }
342
343     private DbLibService getDbLibService() {
344
345         if (dblibSvc != null) {
346             return(dblibSvc);
347         }
348         // Try to get dblib as an OSGI service
349         BundleContext bctx = null;
350         ServiceReference sref = null;
351
352         Bundle bundle = FrameworkUtil.getBundle(SqlResource.class);
353
354         if (bundle != null) {
355             bctx = bundle.getBundleContext();
356         }
357
358         if (bctx != null) {
359             sref = bctx.getServiceReference(DBLIB_SERVICE);
360         }
361
362         if (sref == null) {
363             LOG.warn("Could not find service reference for DBLIB service (" + DBLIB_SERVICE + ")");
364         } else {
365             dblibSvc = (DbLibService) bctx.getService(sref);
366             if (dblibSvc == null) {
367                 LOG.warn("Could not find service reference for DBLIB service (" + DBLIB_SERVICE + ")");
368             }
369         }
370
371         if (dblibSvc == null) {
372             // Must not be running in an OSGI container. See if you can load it
373             // as a
374             // a POJO then.
375
376             // If $SDNC_CONFIG_DIR/dblib.properties exists, that should
377             // be the properties passed to DBResourceManager constructor.
378             // If not, as default just use system properties.
379             Properties dblibProps = System.getProperties();
380             String cfgDir = System.getenv("SDNC_CONFIG_DIR");
381
382             if ((cfgDir == null) || (cfgDir.length() == 0)) {
383                 cfgDir = "/opt/sdnc/data/properties";
384             }
385
386             File dblibPropFile = new File(cfgDir + "/dblib.properties");
387             if (dblibPropFile.exists()) {
388                 try {
389                     dblibProps = new Properties();
390                     dblibProps.load(new FileInputStream(dblibPropFile));
391                 } catch (Exception e) {
392                     LOG.warn("Could not load properties file " + dblibPropFile.getAbsolutePath(), e);
393
394                     dblibProps = System.getProperties();
395                 }
396             }
397
398             try {
399                 dblibSvc = new DBResourceManager(dblibProps);
400             } catch (Exception e) {
401                 LOG.error("Caught exception trying to create dblib service", e);
402             }
403
404             if (dblibSvc == null) {
405                 LOG.warn("Could not create new DBResourceManager");
406             }
407         }
408
409         return (dblibSvc);
410     }
411
412     @Override
413     public QueryStatus notify(String resource, String action, String key, SvcLogicContext ctx)
414             throws SvcLogicException {
415         if (LOG.isDebugEnabled()) {
416             LOG.debug("SqlResource.notify called with resource=" + resource + ", action=" + action);
417         }
418         return QueryStatus.SUCCESS;
419     }
420
421     @Override
422     public QueryStatus delete(String resource, String key, SvcLogicContext ctx) throws SvcLogicException {
423         return (executeSqlWrite(key, ctx));
424     }
425
426     public QueryStatus update(String resource, String key, Map<String, String> parms, String prefix,
427             SvcLogicContext ctx) throws SvcLogicException {
428         return (executeSqlWrite(key, ctx));
429     }
430
431     private String decryptColumn(String tableName, String colName, byte[] colValue, DbLibService dblibSvc) {
432         String strValue = new String(colValue);
433
434         if (StringUtils.isAsciiPrintable(strValue)) {
435
436             // If printable, not encrypted
437             return (strValue);
438         } else {
439             ResultSet results = null;
440             try (Connection conn = ((DBResourceManager) dblibSvc).getConnection();
441                PreparedStatement stmt = conn.prepareStatement("SELECT CAST(AES_DECRYPT(?, ?) AS CHAR(50)) FROM DUAL")) {
442
443                 stmt.setBytes(1, colValue);
444                 stmt.setString(2, getCryptKey());
445                 results = stmt.executeQuery();
446
447                 if ((results != null) && results.next()) {
448                     strValue = results.getString(1);
449                     LOG.debug("Decrypted value is " + strValue);
450                 } else {
451                     LOG.warn("Cannot decrypt " + tableName + "." + colName);
452                 }
453             } catch (Exception e) {
454                 if (results != null) {
455                     try {
456                         results.close();
457                     } catch (SQLException ignored) {
458
459                     }
460                 }
461                 LOG.error("Caught exception trying to decrypt " + tableName + "." + colName, e);
462             }
463         }
464         return (strValue);
465     }
466
467     public static String getCryptKey() {
468         return (CRYPT_KEY);
469     }
470
471     public static String setCryptKey(String key) {
472         CRYPT_KEY = key;
473         return (CRYPT_KEY);
474     }
475
476     public String parameterizedQuery(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException {
477         DbLibService dblibSvc = getDbLibService();
478         String prefix = parameters.get("prefix");
479         String query = parameters.get("query");
480
481         ArrayList<String> arguments = new ArrayList<String>();
482         for (Entry<String, String> a : parameters.entrySet()) {
483             if (a.getKey().startsWith("param")) {
484                 arguments.add(a.getValue());
485             }
486         }
487
488         try {
489             if (dblibSvc == null) {
490                 return mapQueryStatus(QueryStatus.FAILURE);
491             }
492             if (query.contains("count") || query.contains("COUNT")) {
493                 CachedRowSet results = dblibSvc.getData(query, arguments, null);
494
495                 if (!results.next()) {
496                     return mapQueryStatus(QueryStatus.FAILURE);
497                 }
498
499                 int numRows = results.getInt(1);
500                 ctx.setAttribute(prefix + ".count", String.valueOf(numRows));
501                 if (numRows > 0) {
502                     return "true";
503                 } else {
504                     return "false";
505                 }
506             } else if (query.startsWith("select") || query.startsWith("SELECT")) {
507                 CachedRowSet results = dblibSvc.getData(query, arguments, null);
508                 if (!results.next()) {
509                     return mapQueryStatus(QueryStatus.NOT_FOUND);
510                 } else {
511                     saveCachedRowSetToCtx(results, ctx, prefix, dblibSvc);
512                 }
513             } else {
514                 if (!dblibSvc.writeData(query, arguments, null)) {
515                     return mapQueryStatus(QueryStatus.FAILURE);
516                 }
517             }
518             return mapQueryStatus(QueryStatus.SUCCESS);
519         } catch (SQLException e) {
520             LOG.error("Caught SQL exception", e);
521             return mapQueryStatus(QueryStatus.FAILURE);
522         }
523     }
524
525     protected String mapQueryStatus(QueryStatus status) {
526         String str = status.toString();
527         str = str.toLowerCase();
528         str = str.replaceAll("_", "-");
529         return str;
530     }
531 }