/* * (C) Copyright IBM Corp. 2009 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.plugins.wpml; import java.io.File; import java.io.IOException; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.derby.iapi.error.StandardException; import org.apache.derby.iapi.types.DataValueDescriptor; import com.ibm.gaiandb.GaianDBConfig; import com.ibm.gaiandb.GaianDBProcedureUtils; import com.ibm.gaiandb.GaianResultSetMetaData; import com.ibm.gaiandb.Logger; import com.ibm.gaiandb.Util; import com.ibm.gaiandb.plugins.wpml.schema.AccessLogger; import com.ibm.gaiandb.plugins.wpml.schema.DataSource; import com.ibm.gaiandb.plugins.wpml.schema.IRow; import com.ibm.gaiandb.plugins.wpml.schema.QueryContext; import com.ibm.gaiandb.plugins.wpml.schema.Row; import com.ibm.gaiandb.policyframework.SQLResultFilterX; import com.ibm.watson.pml.PMLException; import com.ibm.watson.pml.pep.IObjectPEP; import com.ibm.watson.pml.pep.StrictTwoStagePEP; /** * Policy-enabled filter of relational schema elements. * Provides an implementation of the filter based on the interface definition * made available by GaianDB for plug-in functionality. * <P> * Instantiates a Policy Decision Point (PDP) and a Policy Enforcement Point (PEP) * for the policy evaluation and connects to the repository to access the policies * that govern the filtering of the data. For now, default file for policies * can be found in C:\PFGpolicies.spl. If no such file exists, then the PDP * attempts to retrieve policies from a jdbc repository, using connection details * found in the wpml.properties file. * <P> * Policy evaluation is performed in 2 steps: first, evaluation of authorization * policies is performed, followed by evaluation of the obligation policies. * <P> * Notes/todo list: * <ol> * <li> Proper logging using the PMLLogger facility (com.ibm.watson.pml.util package) * <li> Update (03/05/2009): the nextQueriedDataSource() method will * be deprecated and possibly replaced with verifyDataSources() or something similar. * Also, the way the QueryContext is passed to policies for evaluation needs to be changed * accordingly * <li> Get configuration for connecting to policy repository a properties file. * </ol> * * @author pzerfos@us.ibm.com, drvyvyan@uk.ibm.com * */ public class GenericPolicyPluginForWPML extends SQLResultFilterX { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009"; // private final static String WPML_PFG_POLICIES_FILE = "C:\\PFGpolicies.spl"; private static final Logger logger = new Logger( "PolicyEnabledFilter", 30 ); private List<String> NON_RESTRICTED_LTS = Arrays.asList("LT0", "LT1", "T1", "SUBQUERY", "GDB_LTNULL", "POLICY", "POLICY_SET", "POLICY_SET_MEMBERSHIP", "PEP", "PEP_ATTRIBUTE", "POLICY_ATTRIBUTE", "POLICY_SET_ATTRIBUTE", "INSTANCE", "PEP_HAS_INSTANCE", "TIMESTAMPS"); private boolean isLogicalTableRestricted = true; /** * metadata on the logical table of the query */ private ResultSetMetaData logicalTableRSMD = null; /** * Number of columns in a row */ private int logicalTableColumnCount = -1; /** * Array of flags to identify the columns of a row that have been */ private int[] queriedColumns = null; private int rowCount = -1; /** * Object of {@link com.ibm.gaiandb.plugins.wpml.schema#QueryContext} anchor class to be used for policy evaluation * All query context information needed in policy evaluation is held in this bean-like structure. */ protected final QueryContext queryContext; /** * Object of {@link com.ibm.gaiandb.plugins.wpml.schema#IRow} anchor class to be used for policy evaluation */ private IRow filterRowObject = null; private static IObjectPEP pep = null; private static Object pepLock = new Object(); /** * Allocate the object pep. Allow subclasses to override. * @return * @throws PMLException */ protected static IObjectPEP allocateObjectPEP() throws PMLException { return new StrictTwoStagePEP("pfg-pep", false); } private static void refreshPolicyCache() { synchronized( pepLock ) { try { if ( null == pep ) { pep = allocateObjectPEP(); pep.addAlias("queryContext", QueryContext.class); pep.addAlias("accessLogger", AccessLogger.class); pep.addAlias("dataSource", DataSource.class); pep.addAlias("filterRowObject", IRow.class); } else { pep.endEvaluations(); pep.clear(); } } catch (PMLException e) { System.err.println("PFG: refreshPolicyCache: Error in endEvaluations/beginEvaluations: " + e); e.printStackTrace(); } } } private static boolean evaluatePEP( ArrayList<Object> oa ) throws PMLException { synchronized( pepLock ) { pep.beginEvaluations(); return pep.evaluate(oa.toArray()); } } /** * Create an object array that will store the objects to be evaluated. * If the QueryContext has been set, then add it as well. * * @return an arraylist for the objects that will be evaluated. It potentially * includes the {@link com.ibm.gaiandb.plugins.wpml.schema.QueryContext} object */ private ArrayList<Object> createObjectArray() { ArrayList<Object> objectArray = new ArrayList<Object>(); if (queryContext != null) { // System.out.println("Adding Query Context!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!: " + queryContext); objectArray.add(queryContext); } return objectArray; } // /** // * Add the individual column objects to the array of objects that will be // * used for evaluation in policies. Besides the composite Row object, // * policies can be written directly for Columns. // * // * @param oa the array list with the objects that will be used for policy evaluation // * // * @param filterRowObj the {@link #Row} object with the queried columns // * // * @return an array list that includes the original objects for policy evaluation, as well // * as new ones for the queried columns (one object for each column) // */ // private ArrayList<Object> addColumnsObjectArray(ArrayList<Object> oa, Row filterRowObj) { // // if (filterRowObj == null) // return oa; // // ArrayList<IColumn> ac = filterRowObj.getColumns(); // Iterator<IColumn> iter = ac.iterator(); // while (iter.hasNext()) { // IColumn col = iter.next(); // if (col != null && oa != null) // oa.add(col); // } // // return oa; // } // Global policy watch-dog thread: Periodically refresh policy cache + monitor policy status and notify UI with any changes. static { // synchronized( pepLock ) { if ( null != pep ) return; } System.out.println("Starting policy watchdog"); new Thread( new Runnable() { public void run() { ArrayList<Object> oa = new ArrayList<Object>(); QueryContext qc = new QueryContext(); oa.add(qc); AccessLogger al = new AccessLogger(); IRow ir = null; try { ir = new Row(new GaianResultSetMetaData(), new int[0]); } catch (Exception e1) { e1.printStackTrace(); } while( GenericPolicyPluginForWPML.class.getName().equals( GaianDBConfig.getPolicyClassNameForSQLResultFilter() ) ) { refreshPolicyCache(); try { Thread.sleep(2000); } catch (InterruptedException e) {} notifyWebDemoWithStatusOnAffiliationAndPolicyChoices(oa, qc, al, ir); try { Thread.sleep(2000); } catch (InterruptedException e) {} notifyWebDemoWithStatusOnAffiliationAndPolicyChoices(oa, qc, al, ir); } } }, "Policy refresher watch-dog for " + GenericPolicyPluginForWPML.class.getSimpleName()).start(); } private static String getLocalAffiliation() { final int dashIdx = GaianDBConfig.getAccessClusters().trim().indexOf('-'); String aff = -1 < dashIdx ? GaianDBConfig.getAccessClusters().trim().substring(0, dashIdx) : "None"; return aff.equals("KISH") ? "Kish" : aff; } // In future for a more extensible base policy class, pass in the query context type (i.e. the object model) public GenericPolicyPluginForWPML() { queryContext = new QueryContext(); queryContext.setLocalAffiliation( getLocalAffiliation() ); } public boolean setForwardingNode( String forwardingNode ) { if ( !isLogicalTableRestricted ) return true; if ( null != forwardingNode ) queryContext.setForwardingNode(forwardingNode); return true; } public boolean setUserCredentials(String credentialsStringBlock) { // Only file-retrieving queries are checked to see if requester is allowed. if ( false == queryContext.getLogicalTable().startsWith( IDENTIFYING_PREFIX_FOR_FILE_RETRIEVING_SUBQUERY_CALLBACK ) ) return true; // if ( null == credentialsStringBlock ) { // logger.logImportant("No authenticated user was resolved: credentials block is null"); // return true; // } // This bit is not generic - the structure of credentialsStringBlock is variable - We should use some object model to decompose this.. ? return setAuthenticatedUserCredentials( new String[] { credentialsStringBlock, credentialsStringBlock, "CLEARANCE-NONE" } ); } private boolean setAuthenticatedUserCredentials( String[] userFields ) { // to be called from setUserCredentials() once byte[] is decrypted if ( !isLogicalTableRestricted ) return true; // System.out.println("SET AUTHENTICATED USR CREDS: " + (null==userFields ? null : Arrays.asList(userFields)) ); String user = "", affiliation = "", clearance = ""; if ( null != userFields ) { user = userFields[0]; affiliation = userFields[1]; clearance = userFields[2]; } // User info for QueryContext is set here queryContext.setRequestor(user); queryContext.setAffiliation(affiliation); queryContext.setSecurityClearance(clearance); // always allow queries from nodes of the same affiliation if ( queryContext.getAffiliation().equals( queryContext.getLocalAffiliation() ) ) return true; if ( null == pep ) return true; try { ArrayList<Object> oa = createObjectArray(); oa.add(new AccessLogger()); /* * 2-step policy evaluation: first do the authorizations * then apply the obligations */ final boolean decision = evaluatePEP(oa); // 1 == 1 ? true : evaluatePEP(oa); // System.out.println("Evaluated policy for setAuthenticatedUserCredentials: " + Arrays.asList(oa) + ", decision: " + decision); String aff = affiliation.toLowerCase(); if ( "us".equals(aff) ) aff = "usa"; // convert affiliation string to match UI spec webDemoEvent("policy-update", "{'affiliation':'"+aff+"','allowed':"+decision+"}", "", ""); return decision; } catch (PMLException e) { System.err.println("PFG: setLogicalTable: policy evaluation error"); e.printStackTrace(); } return true; } /* (non-Javadoc) * @see com.ibm.watson.pml.pfg.plugin.RowFilter#setLogicalTable(java.lang.String, java.sql.ResultSetMetaData) */ public boolean setLogicalTable(String logicalTableName, ResultSetMetaData logicalTableResultSetMetaData) { // System.out.println("Access to logical table: " + logicalTableName); if ( NON_RESTRICTED_LTS.contains(logicalTableName.toUpperCase()) ) { isLogicalTableRestricted = false; return true; } // System.out.println("SET LOGICAL TABLE: " + logicalTableName); // Obtain the schema to be filtered by policies this.logicalTableRSMD = logicalTableResultSetMetaData; try { this.logicalTableColumnCount = logicalTableRSMD.getColumnCount(); } catch (SQLException sqle) { System.err.println("ERROR: PFG: could not retrieve logical column count: " + sqle); sqle.printStackTrace(); } rowCount = 0; if ( null != logicalTableName ) queryContext.setLogicalTable(logicalTableName); // Don't evaluate PEP here - we build queryContext to evaluate it once for the whole query state. (then once per data source and once per row returned) return true; } /* (non-Javadoc) * @see com.ibm.watson.pml.pfg.plugin.RowFilter#setQueriedColumns(int[]) */ public boolean setQueriedColumns(int[] queriedColumns) { if ( !isLogicalTableRestricted ) return true; this.queriedColumns = queriedColumns; /* * Construct the Row object to be used for policy evaluation. * The actual DataValueDescriptors with the data of the fields of the * queried columns are populated upon every call of the filterRow() */ filterRowObject = new Row(logicalTableRSMD, this.queriedColumns); return true; } /* (non-Javadoc) * @see com.ibm.watson.pml.pfg.plugin.RowFilter#nextQueriedDataSource(java.lang.String, int[], java.lang.String) */ public int nextQueriedDataSource(String dataSource, int[] columnMappings) { // TODO: Make 'Allow' policy be evaluated here: This will require us to keep a cache of the known affiliations of connected nodes // established at discovery time - we can maintain this in GaianDBConfig.java - then we can look up an affiliation for a given nodeid. // TODO: ? Issue with policy on data source being applied in precedence to predicate for targeting a node. // We don't evaluate data source policies in this demo if ( 1 == 1) return -1; if ( !isLogicalTableRestricted ) return -1; // System.out.println("authenticated user: " + user + ", datasource: " + dataSource); // if ( null != user && user.equals("ibmuser1") && -1 != dataSource.indexOf("datafile") ) { // isLogicalTableRestricted = false; // no more restrictions after this one // return 3; // simple test case, return 3 to signify only 3 rows may be extracted from this source // } if ( null == pep ) return -1; // allow all rows to be extracted // return -1; /* * Perform a policy-based evaluation on whether the data source should * be queried or not. Default is true. If evaluation fails, then do not * query this data source. */ try { ArrayList<Object> oa = createObjectArray(); oa.add(new DataSource(dataSource)); // oa.add(new AccessLogger()); /* * 2-step policy evaluation: first do the authorizations * then apply the obligations */ final boolean decision = evaluatePEP(oa); // 1 == 1 ? true : evaluatePEP(oa); // System.out.println("Evaluated policy for nextQueriedDataSource: " + Arrays.asList(oa) + ", decision: " + decision); return decision ? -1 : 0; } catch (PMLException e) { System.err.println("PFG: nextQueriedDataSource: policy evaluation error"); e.printStackTrace(); } return 0; // don't allow any rows to be extracted } private static final String IDENTIFYING_PREFIX_FOR_FHE_SEARCH_SUBQUERY = "select FHE_SEARCH('"+GaianDBConfig.getGaianNodeID(); private static final String IDENTIFYING_PREFIX_FOR_FILE_RETRIEVING_SUBQUERY_CALLBACK = "select 0 isEncrypted, getFileBZ("; // "from new com.ibm.db2j.GaianQuery('select 0 isEncrypted, getFileBZ("; public static final String WEB_UI_UPDATE_LOCK = "WEB_UI_UPDATE_LOCK"; /* (non-Javadoc) * @see com.ibm.watson.pml.pfg.plugin.RowFilter#filterRow(org.apache.derby.iapi.types.DataValueDescriptor[]) */ public boolean filterRow(DataValueDescriptor[] row) { if ( !isLogicalTableRestricted ) return true; /* * Sanity: check for the number of columns in the record */ if (row.length < logicalTableColumnCount) { System.err.println("ERROR: PFG: invalid fetched row: expecting " + logicalTableColumnCount + " columns, instead of " + row.length); return false; } rowCount++; /* * Set the data of the queried columns in the row that will * be evaluated */ if (filterRowObject != null) { ((Row)filterRowObject).setRowData(row); ((Row)filterRowObject).setRowIndex(rowCount); } if ( null == pep ) return true; final String ltExpression = queryContext.getLogicalTable(); final String fNode = queryContext.getForwardingNode(); System.out.println("forwardingNode: " + fNode + ", ltExpression: " + ltExpression); if ( null == ltExpression ) return true; if ( ltExpression.startsWith( IDENTIFYING_PREFIX_FOR_FHE_SEARCH_SUBQUERY ) && (null == fNode || 1 > fNode.length()) ) { try { final String fromNode = row[1].getString(); // TODO: remove this condition if/when aborting queries earlier in nextQueriedDataSource() if ( row[0].isNull() ) { System.out.println("Result blob from node '" + fromNode + "' is Null - rejecting row"); return false; // discard this empty result - the endpoint node was maybe not allowed to receive the query } // returningAnalyticResult - end webDemoEventReceivingData( fromNode, "", "result", "end", "" ); } catch (StandardException e) { System.out.println("Unable to get provenance nodeID (=row[1]) from outer FHE query result - cannot notify UI"); e.printStackTrace(); } } // int idx = ltExpression.indexOf( IDENTIFYING_FRAGMENT_FOR_FILE_RETRIEVING_QUERY ); // if ( -1 < idx ) System.out.println("==================================================================> BEGIN INVOKING POLICY FOR A GETFILEBZ() QUERY"); // Only apply record modifying policies to queries requesting files (e.g. call-back queries for the outer FHE one) if ( false == ltExpression.startsWith( IDENTIFYING_PREFIX_FOR_FILE_RETRIEVING_SUBQUERY_CALLBACK ) ) return true; // Also only apply policy on records that were generated at our node try { if ( false == GaianDBConfig.getGaianNodeID().equals( row[2].getString() ) ) return true; } catch (StandardException e1) { System.out.println("PFG Unable to check provenance node of data record, cause: " + e1); e1.printStackTrace(); } /* * Perform a policy-based filtering on the record. */ boolean decision = true; // this is overwritten by the default value of the StrictTwoStagePEP (2nd argument of it's constructor) try { ArrayList<Object> oa = createObjectArray(); if (filterRowObject != null) oa.add(filterRowObject); // System.out.println("About to apply row filter for individual row using ObjectArray: " + // Arrays.asList( oa.toArray(new Object[0])) ); /* * 2-step policy evaluation: first do the authorizations * then apply the obligations */ decision = evaluatePEP(oa); // 1 == 1 ? decision : evaluatePEP(oa); // System.out.println("Evaluation for row filter " + Arrays.asList(oa) + ", decision: " + decision); } catch (PMLException e) { System.err.println("PFG: policy evaluation error" + e.getMessage()); e.printStackTrace(); } // None of the code below should be required for a Generic Plugin for WPML. // The encryption action should be re-factored into WPML resource model. final String affiliationAtOriginNode = queryContext.getAffiliation(); // queryContext.getAccessClustersAtOriginNode() // TODO: Remove this when WPML is actually making the decisions based on the queryContext and record that came through. // Encrypt file if the requesting node's cluster IDs is NOT one of the TRUSTED_COALLITION_CLUSTER_IDS (i.e. disjunction is empty). // final List<String> TRUSTED_COALLITION_CLUSTER_IDS = Arrays.asList( "Kish", "US" ); // decision = false == Collections.disjoint( affiliationsAtOriginNode, TRUSTED_COALLITION_CLUSTER_IDS ); // System.out.println("==================================================================> END INVOKING POLICY FOR A GETFILEBZ() QUERY"); System.out.println("AffiliationAtOriginNode: " + affiliationAtOriginNode + ", isTrusted policy decision: " + decision); String aff = affiliationAtOriginNode.toLowerCase(); if ( "us".equals(aff) ) aff = "usa"; // convert affiliation string to match UI spec webDemoEvent("policy-update", "{'affiliation':'"+aff+"','trusted':"+decision+"}", "", ""); int idx = ltExpression.indexOf("'", ltExpression.indexOf("getFileBZ")); String fPath = ltExpression.substring( idx+1, ltExpression.indexOf("'", idx+1) ); // TODO: In future re-factor this to WPML, when it can do the policy action itself (i.e. execute the required system call when its decision is false) if ( false == decision ) { // FHE encrypt - Replace file with its encrypted counter-part. final String fPathEncrypted = fPath.substring(0, fPath.length()-3) + "ctxt"; System.out.println("Path arg of requested file is: " + fPath ); System.out.println("Path of pre-encrypted file is: " + fPathEncrypted ); try { row[1].setValue( GaianDBProcedureUtils.readAndZipFileBytes( new File(fPathEncrypted) ) ); } catch (Exception e) { logger.logAlways("Policy plugin issue: Cannot load new byte[] from encrypted bytes in file: '" + fPathEncrypted + "': " + e); } fPath = fPathEncrypted; // try { // // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // FileInputStream fis = new FileInputStream(fPathEncrypted); // // Util.copyBinaryData(fis, new GZIPOutputStream(baos)); // must be zipped (as we unzip on the other side) // byte[] preEncryptedBytesToSend = baos.toByteArray(); // //// numbytes1 = fileBytes.length; // row[1].setValue( preEncryptedBytesToSend ); // baos.close(); fis.close(); // //// System.out.println("Encrypted row[1] bytes. Initial bytes[] length: " + numbytes0 + ", encrypted bytes[] length: " + numbytes1); // } // catch (Exception e) { logger.logAlways("Policy plugin issue: Cannot load new byte[] from encrypted bytes in file: '" + fPathEncrypted + "': " + e); } } // sendingDataObjectForAnalysis - start webDemoEventSendingData( queryContext.getForwardingPath().get(0), fPath, "query", "start", "" ); // .substring(fPath.lastIndexOf('/')+1) // Also return decision in first column - this indicates whether isEncrypted is true System.out.println("-> Policy does " + (decision?"not ":"") + "require filter/encryption on return value from getFileBZ() function. " + "Setting row[0] to value: " + (decision ? 0 : 1) ); // decision == false means not trusted, so it's encrypted (isEncrypted (row[0]) status 1) try { row[0].setValue( decision ? 0 : 1 ); } catch (StandardException e) { System.err.println("Policy.filterRow(): Unable to set 'isEncrypted' column value (exception trace below)."); e.printStackTrace(); } return true; } private static final String[] KNOWN_AFFILIATIONS = { "US", "UK", "Kish" }; private static boolean[] policyAllowedDecisions = { false, false, false }; // respective initial decisions for US/UK/Kish private static boolean[] policyTrustedDecisions = { false, false, false }; // respective initial decisions for US/UK/Kish private static boolean isInitialPoliciesStatusCheck = true; private static final void notifyWebDemoWithStatusOnAffiliationAndPolicyChoices( ArrayList<Object> oa, QueryContext qc, AccessLogger al, IRow ir ) { final int dashIdx = GaianDBConfig.getAccessClusters().trim().indexOf('-'); String myAffiliation = -1 < dashIdx ? GaianDBConfig.getAccessClusters().trim().substring(0, dashIdx) : "None"; myAffiliation = myAffiliation.toLowerCase(); if ( "us".equals(myAffiliation) ) myAffiliation = "usa"; // convert affiliation string to match UI spec // state our current affiliation webDemoEvent("declare-existence", "{'affiliation':'"+myAffiliation+"'}", "", ""); int i = -1; //// for each affiliation: { for policy in (allow, trust): setup structures required; evaluate; } notify of all decisions for each affiliation to the UI for ( String affiliation : KNOWN_AFFILIATIONS ) { i++; // index in affiliation decisions arrays String aff = affiliation.toLowerCase(); if ( "us".equals(aff) ) aff = "usa"; // convert affiliation string to match UI spec if ( myAffiliation.equals(aff) ) continue; // ignore policies about our own affiliation qc.setAffiliation(affiliation); qc.setLocalAffiliation(getLocalAffiliation()); // local affiliation may change any time based on node setup boolean isAllowed = false, isTrusted = false; try { // System.out.println("queryContext.getLocalAffiliation(): " + queryContext.getLocalAffiliation() // + ", queryContext.getAffiliation(): " + queryContext.getAffiliation() ); oa.add(al); isAllowed = evaluatePEP(oa); // 1 == 1 ? true : evaluatePEP(oa); oa.remove(al); // System.out.println("Evaluated policy for setAuthenticatedUserCredentials: " + Arrays.asList(oa) + ", decision: " + decision); } catch (Exception e) { System.err.println("Exception in notifyWebDemoWithPolicyStatus(), evaluating isAllowed() for affiliation: " + affiliation + ", cause: " + e); e.printStackTrace(); } try { oa.add(ir); isTrusted = evaluatePEP(oa); // 1 == 1 ? decision : evaluatePEP(oa); oa.remove(ir); // System.out.println("Evaluation for row filter " + Arrays.asList(oa) + ", decision: " + decision); } catch (Exception e) { System.err.println("Exception in notifyWebDemoWithPolicyStatus(), evaluating isTrusted() for affiliation: " + affiliation + ", cause: " + e); e.printStackTrace(); } if ( isInitialPoliciesStatusCheck || isAllowed != policyAllowedDecisions[i] || isTrusted != policyTrustedDecisions[i] ) { webDemoEvent("policy-update", "{'affiliation':'"+aff+"','allowed':"+isAllowed+",'trusted':"+isTrusted+"}", "", ""); policyAllowedDecisions[i] = isAllowed; policyTrustedDecisions[i] = isTrusted; } } isInitialPoliciesStatusCheck = false; } /** * Emit demo event info to a Web UI to display what is going on. * * @param nodeID - the node for who the event is emitted. A node may emit an event on behalf of another node. * @param eventID - identifies the event that just occurred in the demo scenario. * @param eventDataJSON - holds data elements relating to the event and needed to describe it. * @param status - start or end. May also be an empty string if the event is immediate. * @param msg - optional message to describe the event in the UI console. */ private static final void webDemoEvent( final String nodeID, final String eventID, final String eventDataJSON, final String status, final String msg ) { String sysCmd = System.getenv("WEB_DEMO_EVENT_CMD"); // final String webDemoEventURL = System.getenv("WEB_DEMO_EVENT_URL"); if ( null == sysCmd ) { if ( false == eventID.equals("declare-existence") ) System.out.println("Unable to notify Web UI (eventID: " + eventID + ", from node: " + nodeID + "): Environment variable 'WEB_DEMO_EVENT_CMD' is not set"); return; } // final String[] sysCmd = new String[] { // "curl", "-H", "\"Content-Type: application/json\"", "-X", "POST", "-d", // "\"{'nodeid':'"+nodeID+"', 'eventid':'"+eventID+"', 'eventdata':"+eventDataJSON+", 'status':'"+status+"', 'timestamp':"+System.currentTimeMillis()+", 'message':'"+msg+"'}\"", // webDemoEventURL // }; String json = ("{'nodeid':'"+nodeID+"', 'eventid':'"+eventID+"', 'eventdata':"+eventDataJSON +", 'status':'"+status+"', 'timestamp':"+System.currentTimeMillis()+", 'message':'"+msg+"'}").replace('\'', '"'); sysCmd = sysCmd.replaceFirst("<JSON>", "'"+json+"'"); // System.out.println("sysCmd: " + sysCmd); final String[] cmdElmts = Util.splitByTrimmedDelimiterNonNestedInCurvedBracketsOrQuotes( sysCmd, ' '); // Remove wrapping quotes that may exist around tokens for ( int i=0; i<cmdElmts.length; i++ ) { String e = cmdElmts[i]; char c = e.charAt(0); if ( '\'' == c || '"' == c ) cmdElmts[i] = e.substring(1,e.length()-1); } if ( false == eventID.equals("declare-existence") ) { StringBuilder sb = new StringBuilder(); for ( String s : cmdElmts ) sb.append(s + " "); System.out.println( sb ); } try { Util.runSystemCommand( cmdElmts, WEB_UI_UPDATE_LOCK, Logger.logLevel > Logger.LOG_LESS ); } catch (IOException e) { System.out.print("Exception in webDemoEvent. nodeID: " + nodeID + ", eventID: " + eventID + ", cause: " + e); e.printStackTrace(); } } public static final void webDemoEvent( final String eventID, final String eventDataJSON, final String status, final String msg ) { webDemoEvent( GaianDBConfig.getGaianNodeID(), eventID, eventDataJSON, status, msg ); } public static final void webDemoEventSendingData( final String toNodeID, final String fileName, final String protocolStep, final String status, final String msg ) { final String localNodeID = GaianDBConfig.getGaianNodeID(); webDemoEvent("sending-data", "{'src':'"+localNodeID+"','dest':'"+toNodeID+"','imageurl':'"+fileName +"','protocol-step':'"+protocolStep+"'}", status, msg); // emit reciprocal event on behalf of the receiving node webDemoEvent(toNodeID, "receiving-data", "{'src':'"+localNodeID+"','dest':'"+toNodeID+"','imageurl':'"+fileName +"','protocol-step':'"+protocolStep+"'}", status, msg); } public static final void webDemoEventReceivingData( final String fromNodeID, final String fileName, final String protocolStep, final String status, final String msg ) { final String localNodeID = GaianDBConfig.getGaianNodeID(); webDemoEvent("receiving-data", "{'src':'"+fromNodeID+"','dest':'"+localNodeID+"','imageurl':'"+fileName +"','protocol-step':'"+protocolStep+"'}", status, msg); // emit reciprocal event on behalf of the sending node webDemoEvent(fromNodeID, "sending-data", "{'src':'"+fromNodeID+"','dest':'"+localNodeID+"','imageurl':'"+fileName +"','protocol-step':'"+protocolStep+"'}", status, msg); } /* (non-Javadoc) * @see com.ibm.watson.pml.pfg.plugin.RowFilter#close() */ public void close() { // try { // if (null != pep) { // pep.endEvaluations(); // pep = null; // } // } catch (PMLException e) { // System.err.println("PEP call to endEvaluations() failed (ignored), cause: " + e); // } rowCount = 0; } @Override public DataValueDescriptor[][] filterRowsBatch(String dataSourceID, DataValueDescriptor[][] rows) { List<DataValueDescriptor[]> newRows = null; int rowIdx=0; // filter through rows[][] - if none are rejected then we'll just return the same array. for ( ; rowIdx < rows.length; rowIdx++ ) if ( filterRow(rows[rowIdx]) ) continue; else { newRows = new ArrayList<DataValueDescriptor[]>(); for ( int i=0; i<rowIdx; i++ ) newRows.add( rows[i] ); rowIdx++; break; } // complete filtering rows[][] - this only applies if one of the rows was rejected for ( ; rowIdx < rows.length; rowIdx++ ) { DataValueDescriptor[] row = rows[rowIdx]; if ( filterRow(row) ) newRows.add( row ); } return null == newRows ? rows : (DataValueDescriptor[][]) newRows.toArray( new DataValueDescriptor[0][] ); } @Override public int nextQueriedDataSource(String dataSourceID, String dataSourceDescription, int[] columnMappings) { return nextQueriedDataSource(dataSourceDescription, columnMappings); } // Only called when invoking VTI Data-source wrappers - return number of records that can be returned (or -1 if unlimited) @Override public int setDataSourceWrapper(String wrapperID) { return -1; } @SuppressWarnings("unchecked") @Override protected Object executeOperationImpl(String opID, Object... args) { // TODO: Obtain authenticated derby user + forwarding path + access cluster ids of query's entry-point node. // These may cause the query to be aborted here, or may trigger filtering obligations when returning records // System.out.println("Entered executeOperation(), opID: " + opID + ", args: " + (null == args ? null : Arrays.asList(args)) ); if ( opID.equals(SQLResultFilterX.OP_ID_SET_AUTHENTICATED_DERBY_USER_RETURN_IS_QUERY_ALLOWED) ) queryContext.setRequestor( (String) args[0] ); else if ( opID.equals(SQLResultFilterX.OP_ID_SET_ACCESS_CLUSTERS_RETURN_IS_QUERY_ALLOWED) ) queryContext.setAccessClustersAtOriginNode( (List<String>) args[0] ); else if ( opID.equals(SQLResultFilterX.OP_ID_SET_FORWARDING_PATH_RETURN_IS_QUERY_ALLOWED) ) { queryContext.setForwardingPath( (List<String>) args[0] ); System.out.println("Set forwarding path: " + Arrays.asList( queryContext.getForwardingPath() ) ); } else return null; // Not recognized - not a piece of info we can use - just ignore // Don't evaluate PEP here - we build queryContext to evaluate it once for the whole query state. (then once per data source and once per row returned) return new Boolean( true ); } }