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