Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
tutorial:dev:conector_-_how_to_implement_connector [2019/01/07 15:34] hanakp |
tutorial:dev:conector_-_how_to_implement_connector [2020/06/10 08:07] kucerar licence |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Connector - Implementation of a new ConnID connector ====== | ||
+ | |||
+ | In this tutorial, we will see how to implement connector. Our example is CSV Connector. | ||
+ | |||
+ | ===== 1. Generate connector from archetype ===== | ||
+ | |||
+ | ==== First option without downloading ==== | ||
+ | Other option is to use this command to generate skeleton for new connector. You don't need to clone the repository and this should work, because the connector-archetype artifact is available in standard maven repository. | ||
+ | < | ||
+ | After this you will be prompted to fill additional information about your connector. See picture in Second option how to create connector | ||
+ | |||
+ | ==== Second option with downloading the artifact to your machine ==== | ||
+ | We do not have start whole project from scratch. What we need to is: | ||
+ | * Download or clone archetype - https:// | ||
+ | * Move to the location of downloaded archetype (where pom.xml is situated) | ||
+ | * Now in this directory just type - 'mvn install' | ||
+ | {{ : | ||
+ | * After we have this setup, we can start to generate the schema. Just type 'mvn archetype: | ||
+ | * We have to go through point-to-point creation of our connector as you can see in the picture under Few other steps to go through in the picture below in Second option how to create connector. | ||
+ | {{ : | ||
+ | * Here we have to find number of the ' | ||
+ | {{ : | ||
+ | * Few other steps to go through in the picture below | ||
+ | {{ : | ||
+ | |||
+ | The basic skeleton for our connector is now done. We can open it in our IDEA. | ||
+ | {{ : | ||
+ | |||
+ | <note important> | ||
+ | If you implement the connector in Eclipse, you may have problems when starting CzechIdM - see [[devel: | ||
+ | </ | ||
+ | |||
+ | ===== 2. Licence for new connector ===== | ||
+ | When you have skeleton for the new connector from artifact, you need to created two files if you have plans to edit any file which was generated. Your new connector will be under CDDL and Apache licence. Requirements are changelog file and licence file. | ||
+ | |||
+ | In root directory for you connector create: | ||
+ | * LICENCE - example https:// | ||
+ | * CHANGELOG.md - example https:// | ||
+ | |||
+ | |||
+ | ===== 3. Implement generated schema ===== | ||
+ | |||
+ | Now everything is generated so we can start to implement the connector. First, we need to change the name of our Sample classes. Let's say we change these names to // | ||
+ | |||
+ | It is important to know, how to work with connectors in CzechIdM. We need to follow steps in Idm to implement correctly our connector. First what we do, when we want the new connector to be connected to Idm, is to import this connector into CzechIdm. For our usage, we can just build our connector with maven goal: //mvn package// where the connector is located. Then build also Idm and before running just paste connector' | ||
+ | |||
+ | Also we need to use predefined path: " | ||
+ | <code java> | ||
+ | # Defines packages where will module search for available connectors | ||
+ | # You can use multivalues (ic.localconnector.packages=net.tirasa.connid.bundles.db, | ||
+ | # You can define only start of package | ||
+ | ic.localconnector.packages=net.tirasa.connid, | ||
+ | </ | ||
+ | |||
+ | First of all, we will look at **CSVConnConfiguration**. | ||
+ | |||
+ | ==== Configuration ==== | ||
+ | |||
+ | <code java> | ||
+ | public class CSVConnConfiguration extends AbstractConfiguration { | ||
+ | |||
+ | /** | ||
+ | * Separator of CSV file for each column | ||
+ | */ | ||
+ | private String separator = ";"; | ||
+ | |||
+ | /** | ||
+ | * encoding of CSV file | ||
+ | */ | ||
+ | private String encoding = " | ||
+ | |||
+ | /** | ||
+ | * path to CSV file | ||
+ | */ | ||
+ | private String sourcePath; | ||
+ | |||
+ | /** | ||
+ | * boolean if CSV file includes header or not | ||
+ | */ | ||
+ | private boolean includesHeader = false; | ||
+ | |||
+ | /** | ||
+ | * if file doesn' | ||
+ | */ | ||
+ | private String[] header; | ||
+ | |||
+ | /** | ||
+ | * Have to be set identifier = __UID__ | ||
+ | */ | ||
+ | private String uid; | ||
+ | |||
+ | /** | ||
+ | * Have to be set __NAME__ | ||
+ | */ | ||
+ | private String name; | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public String getSeparator() { | ||
+ | return separator; | ||
+ | } | ||
+ | |||
+ | public void setSeparator(String separator) { | ||
+ | this.separator = separator; | ||
+ | } | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public String getEncoding() { | ||
+ | return encoding; | ||
+ | } | ||
+ | |||
+ | public void setEncoding(String encoding) { | ||
+ | this.encoding = encoding; | ||
+ | } | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public boolean isIncludesHeader() { | ||
+ | return includesHeader; | ||
+ | } | ||
+ | |||
+ | public void setIncludesHeader(boolean includesHeader) { | ||
+ | this.includesHeader = includesHeader; | ||
+ | } | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public String getSourcePath() { | ||
+ | return sourcePath; | ||
+ | } | ||
+ | |||
+ | public void setSourcePath(String sourcePath) { | ||
+ | this.sourcePath = sourcePath; | ||
+ | } | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public String[] getHeader() { | ||
+ | return header; | ||
+ | } | ||
+ | |||
+ | public void setHeader(String[] header) { | ||
+ | this.header = header; | ||
+ | } | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public String getUid() { | ||
+ | return uid; | ||
+ | } | ||
+ | |||
+ | public void setUid(String uid) { | ||
+ | this.uid = uid; | ||
+ | } | ||
+ | |||
+ | @ConfigurationProperty(displayMessageKey = " | ||
+ | helpMessageKey = " | ||
+ | public String getName() { | ||
+ | return name; | ||
+ | } | ||
+ | |||
+ | public void setName(String name) { | ||
+ | this.name = name; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void validate() { | ||
+ | |||
+ | if (StringUtil.isBlank(separator) || separator == null) { | ||
+ | throw new ConfigurationException(" | ||
+ | } | ||
+ | |||
+ | if (StringUtil.isBlank(encoding) || encoding==null) { | ||
+ | throw new ConfigurationException(" | ||
+ | } | ||
+ | |||
+ | if (StringUtil.isBlank(sourcePath) || sourcePath==null) { | ||
+ | throw new ConfigurationException(" | ||
+ | } | ||
+ | |||
+ | if(!includesHeader && header==null){ | ||
+ | throw new ClassCastException(" | ||
+ | } | ||
+ | |||
+ | if(StringUtil.isBlank(uid) || uid==null){ | ||
+ | throw new ConfigurationException(" | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | As you can see in the code it is just pile of getters and setters. | ||
+ | * **Variables** are basically properties which are filled in the frontend. There need to be setters and getters for each. | ||
+ | * **Annotations** are mostly necessary for name tag and help tag. Both can be then filled in // | ||
+ | * If you need to pre-fill some of the variables, just fill it here. | ||
+ | * **Method validate()** is method for //Test Connector// in connector' | ||
+ | * Order in the annotation is just for order in frontend page. | ||
+ | |||
+ | ==== Connector ==== | ||
+ | This class is the brain of the connector. | ||
+ | |||
+ | === Init === | ||
+ | Initialize configuration filled by user. | ||
+ | <code java> | ||
+ | @Override | ||
+ | public void init(final Configuration configuration) { | ||
+ | this.configuration = (CSVConnConfiguration) configuration; | ||
+ | LOG.ok(" | ||
+ | } | ||
+ | </ | ||
+ | Now we need to somehow generate the schema. | ||
+ | |||
+ | === Schema === | ||
+ | |||
+ | Schema function generates the new schema in IDM. The main problem of this function is to analyze all columns and find identifier. In our example, we have to see the header of the CSV file. It can be found externally or we have to see the header in the file. | ||
+ | |||
+ | <code java> | ||
+ | @Override | ||
+ | public Schema schema() { | ||
+ | return new CreateSchema(configuration).generateSchema(this); | ||
+ | } | ||
+ | </ | ||
+ | <code java> | ||
+ | /** | ||
+ | * This class creates schema from given CSV file (Configuration path). | ||
+ | * @author Marek Klement | ||
+ | */ | ||
+ | public class CreateSchema { | ||
+ | |||
+ | private final CSVConnConfiguration conf; | ||
+ | private static final Log LOG = Log.getLog(CreateSchema.class); | ||
+ | |||
+ | public CreateSchema(CSVConnConfiguration conf){ | ||
+ | this.conf = conf; | ||
+ | } | ||
+ | |||
+ | public Schema generateSchema(Connector connector){ | ||
+ | String[] header = null; | ||
+ | final CSVReader reader; | ||
+ | final CSVParser parser; | ||
+ | try { | ||
+ | parser = new CSVParserBuilder() | ||
+ | .withSeparator(conf.getSeparator().charAt(0)) | ||
+ | .withIgnoreQuotations(true) | ||
+ | .build(); | ||
+ | reader = new CSVReaderBuilder(new FileReader(conf.getSourcePath())) | ||
+ | .withCSVParser(parser) | ||
+ | .build(); | ||
+ | Iterator< | ||
+ | if(conf.isIncludesHeader()){ | ||
+ | header = findHeader(it); | ||
+ | } else if(!conf.isIncludesHeader() && conf.getHeader()!=null){ | ||
+ | checkLength(it, | ||
+ | header = conf.getHeader(); | ||
+ | } else { | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | reader.close(); | ||
+ | } catch (FileNotFoundException e) { | ||
+ | e.printStackTrace(); | ||
+ | } catch (IOException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | return createSchema(connector, | ||
+ | } | ||
+ | |||
+ | private String[] findHeader(Iterator< | ||
+ | String[] header; | ||
+ | if(it.hasNext()){ | ||
+ | header = it.next(); | ||
+ | checkLength(it, | ||
+ | } else { | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | LOG.ok(" | ||
+ | return header; | ||
+ | } | ||
+ | |||
+ | private void checkLength(Iterator< | ||
+ | while (it.hasNext()){ | ||
+ | String[] next = it.next(); | ||
+ | if(next.length!=length){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | ++i; | ||
+ | } | ||
+ | LOG.ok(" | ||
+ | } | ||
+ | |||
+ | private Schema createSchema(Connector connector, String[] header){ | ||
+ | SchemaBuilder sb = new SchemaBuilder(connector.getClass()); | ||
+ | final Set< | ||
+ | if(conf.getName()==null){ | ||
+ | conf.setName(conf.getUid()); | ||
+ | } | ||
+ | for(String column : header) { | ||
+ | final AttributeInfoBuilder attributeInfoBuilder = new AttributeInfoBuilder(); | ||
+ | if(column.equals(conf.getUid()) || column.equals(conf.getName())){ | ||
+ | attributeInfoBuilder.setRequired(true); | ||
+ | |||
+ | } | ||
+ | attributeInfoBuilder.setName(column); | ||
+ | attributeInfoBuilder.setCreateable(true); | ||
+ | attributeInfoBuilder.setUpdateable(true); | ||
+ | attributeInfos.add(attributeInfoBuilder.build()); | ||
+ | } | ||
+ | sb.defineObjectClass(ObjectClass.ACCOUNT_NAME, | ||
+ | LOG.ok(" | ||
+ | return sb.build(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Create === | ||
+ | |||
+ | This method is the call for creating new item in the CSV file. It should find the end of the CSV file and put the new item with all attributes on the bottom of the file. | ||
+ | |||
+ | <code java> | ||
+ | @Override | ||
+ | public Uid create( | ||
+ | final ObjectClass objectClass, | ||
+ | final Set< | ||
+ | final OperationOptions options) { | ||
+ | try { | ||
+ | return new CreateItem(configuration).createItem(objectClass, | ||
+ | } catch (IOException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | </ | ||
+ | <code java> | ||
+ | /** | ||
+ | * Class for creating items in CSV file from system | ||
+ | * @author Marek Klement | ||
+ | */ | ||
+ | public class CreateItem extends Operations { | ||
+ | |||
+ | private static final Log LOG = Log.getLog(CreateItem.class); | ||
+ | |||
+ | public CreateItem(CSVConnConfiguration conf) { | ||
+ | super(conf); | ||
+ | } | ||
+ | |||
+ | public Uid createItem(ObjectClass objectClass, | ||
+ | String name = findName(createAttributes); | ||
+ | if(name == null || StringUtil.isBlank(name)){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | if(userExists(name)){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | addItem(createAttributes); | ||
+ | LOG.ok(" | ||
+ | return new Uid(name); | ||
+ | } | ||
+ | |||
+ | private void addItem(Set< | ||
+ | final CSVWriter writer = new CSVWriter(new FileWriter(conf.getSourcePath(), | ||
+ | String[] nextLine = createNewLine(createAttributes); | ||
+ | writer.writeNext(nextLine); | ||
+ | writer.close(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Delete === | ||
+ | |||
+ | Operation delete is almost same as create(). Just with the difference that instead of creating new item we simply delete the item with given Uid from CSV file. | ||
+ | |||
+ | <code java> | ||
+ | @Override | ||
+ | public void delete( | ||
+ | final ObjectClass objectClass, | ||
+ | final Uid uid, | ||
+ | final OperationOptions options) { | ||
+ | try { | ||
+ | new DeleteItem(configuration).deleteItem(objectClass ,uid, options); | ||
+ | } catch (IOException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <code java> | ||
+ | /** | ||
+ | * Class for delleting items in CSV file | ||
+ | * @author marek | ||
+ | */ | ||
+ | public class DeleteItem extends Operations { | ||
+ | |||
+ | private static final Log LOG = Log.getLog(CreateItem.class); | ||
+ | |||
+ | public DeleteItem(CSVConnConfiguration conf) { | ||
+ | super(conf); | ||
+ | } | ||
+ | |||
+ | public Uid deleteItem(ObjectClass objectClass, | ||
+ | if(uid == null){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | String name = uid.getUidValue(); | ||
+ | if(name == null || StringUtil.isBlank(name)){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | if(!userExists(name)){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | removeItem(name); | ||
+ | LOG.ok(" | ||
+ | return new Uid(name); | ||
+ | } | ||
+ | |||
+ | private void removeItem(String name) throws IOException { | ||
+ | CSVParser parser = new CSVParserBuilder() | ||
+ | .withSeparator(conf.getSeparator().charAt(0)) | ||
+ | .withIgnoreQuotations(true) | ||
+ | .build(); | ||
+ | CSVReader reader = new CSVReaderBuilder(new FileReader(conf.getSourcePath())) | ||
+ | .withCSVParser(parser) | ||
+ | .build(); | ||
+ | Iterator< | ||
+ | List< | ||
+ | int nameNumber = getNameNumber(headerFound); | ||
+ | while (it.hasNext()){ | ||
+ | String[] line = it.next(); | ||
+ | if(!line[nameNumber].equals(name)){ | ||
+ | buffer.add(line); | ||
+ | System.out.println(line); | ||
+ | } | ||
+ | } | ||
+ | reader.close(); | ||
+ | CSVWriter writer = new CSVWriter(new FileWriter(conf.getSourcePath()), | ||
+ | writer.writeAll(buffer); | ||
+ | writer.close(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Update === | ||
+ | |||
+ | This method takes care of updating items in CSV file. On the connected system, it just finds right identifier value and then updates all attributes. | ||
+ | |||
+ | <code java> | ||
+ | @Override | ||
+ | public Uid update( | ||
+ | final ObjectClass objectClass, | ||
+ | final Uid uid, | ||
+ | final Set< | ||
+ | final OperationOptions options) { | ||
+ | |||
+ | try { | ||
+ | return new UpdateItem(configuration).updateItem(objectClass, | ||
+ | } catch (IOException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | </ | ||
+ | <code java> | ||
+ | /** | ||
+ | * Class for update record in CSV file | ||
+ | * @author Marek Klement | ||
+ | */ | ||
+ | public class UpdateItem extends Operations{ | ||
+ | |||
+ | private static final Log LOG = Log.getLog(CreateItem.class); | ||
+ | |||
+ | public UpdateItem(CSVConnConfiguration conf) { | ||
+ | super(conf); | ||
+ | } | ||
+ | |||
+ | public Uid updateItem(ObjectClass objectClass, | ||
+ | if(uid == null){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | String name = uid.getUidValue(); | ||
+ | if(name == null || StringUtil.isBlank(name)){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | if(!userExists(name)){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | if(updateAttributes==null){ | ||
+ | throw new IllegalArgumentException(" | ||
+ | } | ||
+ | update(updateAttributes, | ||
+ | LOG.ok(" | ||
+ | return new Uid(name); | ||
+ | } | ||
+ | |||
+ | private void update( Set< | ||
+ | int nameNumber = getNameNumber(headerFound); | ||
+ | CSVParser parser = new CSVParserBuilder() | ||
+ | .withSeparator(conf.getSeparator().charAt(0)) | ||
+ | .withIgnoreQuotations(true) | ||
+ | .build(); | ||
+ | CSVReader reader = new CSVReaderBuilder(new FileReader(conf.getSourcePath())) | ||
+ | .withCSVParser(parser) | ||
+ | .build(); | ||
+ | Iterator< | ||
+ | List< | ||
+ | while (it.hasNext()){ | ||
+ | String[] line = it.next(); | ||
+ | if(!line[nameNumber].equals(name)){ | ||
+ | buffer.add(line); | ||
+ | } else { | ||
+ | line = createNewLine(updateAttributes); | ||
+ | buffer.add(line); | ||
+ | } | ||
+ | } | ||
+ | reader.close(); | ||
+ | CSVWriter writer = new CSVWriter(new FileWriter(conf.getSourcePath()), | ||
+ | writer.writeAll(buffer); | ||
+ | writer.close(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Sync & ExecuteQuerry === | ||
+ | |||
+ | This function is mostly brain of the connector. Basically, it reads all items in our CSV file and then it creates objects for the system. It all depends on filter we use. | ||
+ | This function can be called: | ||
+ | * as Sync, where we use to filter | ||
+ | * with no filter | ||
+ | |||
+ | <code java> | ||
+ | @Override | ||
+ | public void sync( | ||
+ | final ObjectClass objectClass, | ||
+ | final SyncToken token, | ||
+ | final SyncResultsHandler handler, | ||
+ | final OperationOptions options) { | ||
+ | LOG.info(" | ||
+ | CSVConnFilter filter = createSyncFilter(token, | ||
+ | ResultsHandler resultsHandler = connectorObject -> { | ||
+ | |||
+ | SyncToken newToken = new SyncToken(connectorObject.getAttributeByName(configuration.getSyncTokenColumn()).getValue().get(0)); | ||
+ | SyncDeltaBuilder builder = new SyncDeltaBuilder(); | ||
+ | builder.setObject(connectorObject) | ||
+ | .setToken(newToken) | ||
+ | //TODO operation DELETE | ||
+ | .setDeltaType(SyncDeltaType.CREATE_OR_UPDATE) | ||
+ | .setObjectClass(objectClass); | ||
+ | return handler.handle(builder.build()); | ||
+ | }; | ||
+ | executeQuery(objectClass, | ||
+ | } | ||
+ | |||
+ | private CSVConnFilter createSyncFilter(SyncToken token, String syncTokenColumn) { | ||
+ | final CSVConnFilter filter = new CSVConnFilter(CSVConnFilter.Operation.GT, | ||
+ | return filter; | ||
+ | } | ||
+ | </ | ||
+ | <code java> | ||
+ | @Override | ||
+ | public void executeQuery( | ||
+ | final ObjectClass objectClass, | ||
+ | final CSVConnFilter query, | ||
+ | final ResultsHandler handler, | ||
+ | final OperationOptions options) { | ||
+ | LOG.info(" | ||
+ | List< | ||
+ | result.forEach(handler:: | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | And now main functionality of this method: | ||
+ | |||
+ | <code java> | ||
+ | /** | ||
+ | * Class for holding functions to parse CSV into object | ||
+ | * @author Marek Klement | ||
+ | */ | ||
+ | public class CreateObjectsFromCSV { | ||
+ | |||
+ | private final CSVConnConfiguration conf; | ||
+ | private final CSVConnFilter filter; | ||
+ | private static final Log LOG = Log.getLog(CreateObjectsFromCSV.class); | ||
+ | |||
+ | public CreateObjectsFromCSV(CSVConnConfiguration conf, CSVConnFilter filter){ | ||
+ | this.conf = conf; | ||
+ | this.filter = filter; | ||
+ | } | ||
+ | |||
+ | public List< | ||
+ | List< | ||
+ | final CSVReader reader; | ||
+ | final CSVParser parser; | ||
+ | try { | ||
+ | parser = new CSVParserBuilder() | ||
+ | .withSeparator(conf.getSeparator().charAt(0)) | ||
+ | .withIgnoreQuotations(true) | ||
+ | .build(); | ||
+ | reader = new CSVReaderBuilder(new FileReader(conf.getSourcePath())) | ||
+ | .withCSVParser(parser) | ||
+ | .build(); | ||
+ | String[] header; | ||
+ | if(conf.isIncludesHeader()){ | ||
+ | header = reader.readNext(); | ||
+ | conf.setHeader(header); | ||
+ | } else { | ||
+ | header = conf.getHeader(); | ||
+ | } | ||
+ | items = new LinkedList<> | ||
+ | Iterator< | ||
+ | if(conf.getName()==null){ | ||
+ | conf.setName(conf.getUid()); | ||
+ | } | ||
+ | LOG.info(" | ||
+ | while (it.hasNext()){ | ||
+ | String[] item = it.next(); | ||
+ | final ConnectorObject obj = transform(header, | ||
+ | if(filter!=null) { | ||
+ | if (filter.evaluate(obj)) { | ||
+ | items.add(obj); | ||
+ | LOG.ok(" | ||
+ | } | ||
+ | } else { | ||
+ | items.add(obj); | ||
+ | } | ||
+ | } | ||
+ | LOG.ok(" | ||
+ | reader.close(); | ||
+ | } catch (FileNotFoundException e) { | ||
+ | e.printStackTrace(); | ||
+ | } catch (IOException e) { | ||
+ | e.printStackTrace(); | ||
+ | } finally { | ||
+ | // | ||
+ | } | ||
+ | return items; | ||
+ | } | ||
+ | |||
+ | private ConnectorObject transform(String [] header, String [] line, String name, String uid) { | ||
+ | ConnectorObjectBuilder builder = new ConnectorObjectBuilder(); | ||
+ | |||
+ | for (int i = 0; i < header.length; | ||
+ | String head = header[i]; | ||
+ | String lin = line[i]; | ||
+ | if(head.equals(name)){ | ||
+ | builder.setName(lin); | ||
+ | } | ||
+ | if(head.equals(uid)){ | ||
+ | builder.setUid(lin); | ||
+ | } | ||
+ | builder.addAttribute(head, | ||
+ | } | ||
+ | return builder.build(); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Filter === | ||
+ | |||
+ | My worst problem was with getting know to filter. A filter is a tool for Synchronization mostly. It can spot last changed an item in CSV file and ignore all before. Lets see my Filter and FilterTranslator: | ||
+ | |||
+ | <code java> | ||
+ | public class CSVFilterTranslator extends AbstractFilterTranslator< | ||
+ | |||
+ | private static final Log LOG = Log.getLog(CSVFilterTranslator.class); | ||
+ | |||
+ | @Override | ||
+ | protected CSVConnFilter createEqualsExpression(final EqualsFilter filter, final boolean not) { | ||
+ | LOG.info(" | ||
+ | return new CSVConnFilter(CSVConnFilter.Operation.EQ, | ||
+ | } | ||
+ | |||
+ | |||
+ | @Override | ||
+ | protected CSVConnFilter createGreaterThanExpression(GreaterThanFilter filter, boolean not) { | ||
+ | LOG.info(" | ||
+ | return new CSVConnFilter(CSVConnFilter.Operation.GT, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | and filter itself: | ||
+ | |||
+ | <code java> | ||
+ | public class CSVConnFilter { | ||
+ | |||
+ | private static final Log LOG = Log.getLog(CSVConnFilter.class); | ||
+ | |||
+ | public enum Operation { | ||
+ | EQ, GT | ||
+ | } | ||
+ | |||
+ | private final Operation operation; | ||
+ | private final String attributeName; | ||
+ | private final Object attributeValue; | ||
+ | |||
+ | public CSVConnFilter(Operation operation, String attributeName, | ||
+ | this.operation = operation; | ||
+ | this.attributeName = attributeName; | ||
+ | this.attributeValue = attributeValue; | ||
+ | } | ||
+ | |||
+ | |||
+ | public boolean evaluate(ConnectorObject obj) { | ||
+ | final Attribute attribute = obj.getAttributeByName(attributeName); | ||
+ | boolean ret; | ||
+ | LOG.info(" | ||
+ | if (attribute == null) { | ||
+ | return false; | ||
+ | } | ||
+ | switch (operation) { | ||
+ | case EQ: | ||
+ | //TODO: multivalued | ||
+ | ret = attributeValue.equals(attribute.getValue().get(0)); | ||
+ | return ret; | ||
+ | case GT: | ||
+ | int bh = attributeValue.toString().compareTo(attribute.getValue().get(0).toString()); | ||
+ | ret = bh<0; | ||
+ | return ret; | ||
+ | default: | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | public Operation getOperation() { | ||
+ | return operation; | ||
+ | } | ||
+ | |||
+ | public String getAttributeName() { | ||
+ | return attributeName; | ||
+ | } | ||
+ | |||
+ | public Object getAttributeValue() { | ||
+ | return attributeValue; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== 4. Add connector to IDM ===== | ||
+ | |||
+ | After everything is written we should add our new connector into IDM. It might look like its hard but actually, it is easy. We just add dependency into app module in CzechIdM app. | ||
+ | So for our example, this is what you should add when we need to use CSV connector: | ||
+ | |||
+ | <code java> | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ===== 5. Setup logging for connector ===== | ||
+ | |||
+ | If you want to log for example INFO messages from connectors you have to add following property to your active profile in logback-spring.xml: | ||
+ | < | ||
+ | <logger name=" | ||
+ | </ | ||
+ | name means the package where you want to setup logs and level sets up types of logs. | ||
+ | For setting of WARN messages you can copy this property and just change level to " | ||
+ | Info messages are switched on in default profile which is used in production, but usually switched off for development. | ||
+ | |||