package me.chrisolive.sailpoint.test; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import sailpoint.api.SailPointContext; import sailpoint.connector.Connector; import sailpoint.connector.DelimitedFileConnector; import sailpoint.object.Application; import sailpoint.object.AttributeDefinition; import sailpoint.object.Schema; import sailpoint.tools.GeneralException; import au.com.bytecode.opencsv.CSVReader; public class SailPointTest { private static Log log = LogFactory.getLog( SailPointTest.class ); // The following constants roughly correspond to the IIQ DelimitedFile // preamble for Account: private static final char QUOTE_CHAR = '"'; private static final char DELIMITER = ','; private static final boolean HAS_COLUMN_NAMES = true; private static final String[] COLUMNS = { "One", "Two", "Three" }; private static final int SKIP_LINES = 0; private static final boolean FILTER_EMPTY = true; // Not implemented here. private static final char COMMENT_CHAR = '#'; // Not implemented here. private static final String FILTER_STRING = ""; // Not implemented here. public SailPointTest() {} // Various method calls for getting a Schema object. // // At least this version of createSchema() should be called as a Schema // object of some sort will be needed to pass to the executeBuildMap() // method. public static Schema createSchema() { return new Schema(); } // Here we can just get a schema of type ACCOUNT or GROUP (once group // methods are implemented. public static Schema createSchema( String type ) { Schema schema = new Schema(); schema.setObjectType( type ); return schema; } // Here is an example of building out a full Schema object if we need // specific Schema data in the build map rule we are testing. public static Schema createSchema( String type, String identityAttribute, String displayAttribute ) { // Allowed method signatures for the Schema.addAttributeDefinition() // method DON'T provide us with a format that matches the IIQ GUI. // I want a method or an AttributeDefinition object that matches the // GUI. So I create a local class that provides the attribute defs // I want in the order I want as represented in the GUI (roughly). // I only need this here and I don't want a separate class to def // this, so here it is. Hopefully the Java purists won't shoot me! @SuppressWarnings( "serial" ) class LocalAttributeDefinition extends AttributeDefinition { // The signature here matches how it looks in the IIQ GUI, roughly // under the Schema tab: // NAME DESCRIPTION TYPE [MANAGED] ENTITLEMENT MULTI-VALUED // // We left out MANAGED in this signature: public LocalAttributeDefinition( String name, String type, String description, boolean entitlement, boolean multi ) { this.setName( name ); this.setType( type ); this.setDescription( description ); this.setEntitlement( entitlement ); this.setMulti( entitlement ); } } // Get a schema instance, set it's type, identity attribute and display // attribute. Schema schema = new Schema(); schema.setObjectType( type ); schema.setIdentityAttribute( identityAttribute ); schema.setDisplayAttribute( displayAttribute ); // Here, we build our custom schema. It's not used in our example, but // we throw in a few attributes to show how to build out each attribute: schema.addAttributeDefinition( new LocalAttributeDefinition( "Username", AttributeDefinition.TYPE_STRING, "", false, false ) ); schema.addAttributeDefinition( new LocalAttributeDefinition( "FullName", AttributeDefinition.TYPE_STRING, "", false, false ) ); schema.addAttributeDefinition( new LocalAttributeDefinition( "CostCenter", AttributeDefinition.TYPE_STRING, "", false, true ) ); // Return the schema we built. return schema; } // Here is where we actually are able to test our build map code from IIQ. // Essentially, we can cut and paste our build map code out of IIQ into // this area of code. If we have embedded functions in our BeanShell code // we would have to refactor a little inside of Java and then break that // logic back out, but essentially we can: // // (1) Check the logic here AND... // (2) Use Eclipse to pick up on other errors, which we can't do in the IIQ // editor!! // // Or... alternately... DEVELOP our build map rule here first and, when we // know it works, cut and paste working code back into IIQ. :-) @SuppressWarnings( { "unchecked", "rawtypes" } ) public static Map executeBuildMap( Log log, SailPointContext context, Application application, Schema schema, Map state, List record, List cols ) { Map map = DelimitedFileConnector.defaultBuildMap( cols, record ); // Fix userName versus fullName issue. String fullName = (String) map.get( "fullName" ); map.put( "userName", fullName ); fullName = fullName.replace( ".", " " ); map.put( "fullName", fullName ); // Transform the email. String email = (String) map.get( "email" ); String newEmail = email.replace( "demoexample.com", "newcompany.com" ); map.put( "email", newEmail.toLowerCase() ); return map; } // Here is an aggregate account task method. It doesn't really represent // a task as it is implemented in IIQ, but logically, this is about what // happens in order to aggregate an account CSV. The CSV file has to be // opened and read and the build map executed for every line in the file. // Even state is implemented here! public static void taskAggregateAccount( String csvFilename ) throws GeneralException, IOException { // Suck in CSV file records using OpenCSV reader object. Correctly // handles reading a CSV file with embedded delimiters inside of // a designated quoted character, etc. Simple. // // Notice how the OpenCSV implementation dovetails nicely with some // of the IIQ preamble items (constants) we've defined as from the // IIQ GUI. :-) CSVReader reader = new CSVReader( new FileReader( csvFilename ), DELIMITER, QUOTE_CHAR, SKIP_LINES ); List records = reader.readAll(); reader.close(); // Null SailPoint context. Establishing a real, live SailPointContext // object is, well... a bit of work, pretty much requires using the // Spring framework, and in most cases, especially for simple rules // modeling, isn't going to be needed, SOOOO... we chicken out. :-) SailPointContext context = null; // Setup application and schema. This is mainly dummied out for now as // we don't have a real live SailPointContext in this example. // // We *can* select at present from a number of Schema object creation // methods, but any schema information would have to be programatically // filled out as shown above. Application application = new Application(); Schema schema = createSchema( Connector.TYPE_ACCOUNT, "Username", "Fullname" ); System.out.println( schema.toXml() ); // Set state. At the beginning of the aggregation, it's empty. Map state = new HashMap(); // Determine columns we are using. We can use the columns based on the // first (readable) line in the file (relative to SKIP_LINES), or we can // use our designated static COLUMNS just like we can in IIQ. List columns = Arrays.asList( COLUMNS ); if (HAS_COLUMN_NAMES) { columns = Arrays.asList( records.get( 0 ) ); records.remove( 0 ); } // Here, we execute our build map for every record in the input CSV file. // Anything we want to do after executing the build map, which... returns // a Map instance just like in IIQ... we can do right after the // executeBuildMap() call. // // This gives us a bit more flexibility that seeing the ResourceObject // painted to the screen as in ConnectorDebug. I can construct the // output here to suit: for (String[] record : records) { Map returnMap = executeBuildMap( log, context, application, schema, state, Arrays.asList( record ), columns ); // For now, I just show key/value pairs from the return map itself: System.out.println( "-----" ); for (String key : returnMap.keySet()) System.out.println( "Key: " + key + " Value: " + returnMap.get( key ) ); } } // Here, we get the filename from Eclipse, run our own special // "Aggregate Account" "task", execute our build map rule and exercise its // logic and then, as above, print the key/value pairs. We could of course // do whatever we want with the return map since we're using real Java: public static void main( String[] args ) throws GeneralException, IOException { taskAggregateAccount( args[0] ); // taskAggregateGroup( args[0] ); <-- We could, if we wanted, implement a group aggregation } }