org.forgerock.openicf.connectors.elastic.ElasticConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.forgerock.openicf.connectors.elastic.ElasticConnector.java

Source

/*
 * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013 ForgeRock Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://forgerock.org/license/CDDLv1.0.html
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at http://forgerock.org/license/CDDLv1.0.html
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 */

package org.forgerock.openicf.connectors.elastic;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.elasticsearch.Version;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.framework.common.objects.*;
import org.identityconnectors.framework.common.objects.filter.FilterTranslator;
import org.identityconnectors.framework.spi.Configuration;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.ConnectorClass;
import org.identityconnectors.framework.spi.operations.*;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;

import static java.util.Arrays.asList;
import static org.elasticsearch.index.query.FilterBuilders.orFilter;
import static org.elasticsearch.index.query.FilterBuilders.rangeFilter;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;

/**
 * Main implementation of the ElasticConnector Connector.
 *
 * @author $author$
 * @version $Revision$ $Date$
 */
@ConnectorClass(displayNameKey = "ElasticConnector", configurationClass = ElasticConfiguration.class)
public class ElasticConnector
        implements Connector, CreateOp, DeleteOp, UpdateOp, SearchOp<FilterBuilder>, SyncOp, TestOp {
    /**
     * Setup logging for the {@link ElasticConnector}.
     */
    private static final Log logger = Log.getLog(ElasticConnector.class);

    /**
     * Place holder for the Connection created in the init method.
     */
    private ElasticConnection connection;

    /**
     * Place holder for the {@link Configuration} passed into the init() method
     * {@link ElasticConnector#init(org.identityconnectors.framework.spi.Configuration)}.
     */
    private ElasticConfiguration configuration;

    /**
     * Gets the Configuration context for this connector.
     *
     * @return The current {@link Configuration}
     */
    public Configuration getConfiguration() {
        return this.configuration;
    }

    /**
     * Callback method to receive the {@link Configuration}.
     *
     * @param configuration the new {@link Configuration}
     * @see org.identityconnectors.framework.spi.Connector#init(org.identityconnectors.framework.spi.Configuration)
     */
    public void init(final Configuration configuration) {
        this.configuration = (ElasticConfiguration) configuration;
        this.connection = new ElasticConnection(this.configuration);
    }

    /**
     * Disposes of the {@link ElasticConnector}'s resources.
     *
     * @see org.identityconnectors.framework.spi.Connector#dispose()
     */
    public void dispose() {
    }

    /******************
     * SPI Operations
     *
     * Implement the following operations using the contract and
     * description found in the Javadoc for these methods.
     ******************/

    /**
     * {@inheritDoc}
     */
    @Override
    public Uid create(final ObjectClass objectClass, final Set<Attribute> createAttributes,
            final OperationOptions options) {
        final Uid uid = new Uid(AttributeUtil.getNameFromAttributes(createAttributes).getNameValue());
        client().prepareIndex(configuration.getIndex(), configuration.getType()).setId(uid.getUidValue())
                .setSource(attributesToBytes(createAttributes)).get();
        return uid;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(final ObjectClass objectClass, final Uid uid, final OperationOptions options) {
        client().prepareDelete(configuration.getIndex(), configuration.getType(), uid.getUidValue()).get();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Uid update(ObjectClass objectClass, Uid uid, Set<Attribute> replaceAttributes,
            OperationOptions options) {
        client().prepareUpdate(configuration.getIndex(), configuration.getType(), uid.getUidValue())
                .setDoc(attributesToBytes(replaceAttributes)).get();
        return uid;
    }

    @Override
    public FilterTranslator<FilterBuilder> createFilterTranslator(ObjectClass oclass, OperationOptions options) {
        return new ElasticFilterTranslator();
    }

    @Override
    public void executeQuery(ObjectClass oclass, FilterBuilder filter, ResultsHandler handler,
            OperationOptions options) {
        final List<ConnectorObject> searchResults = doSearch(oclass, filter, options);
        for (ConnectorObject connectorObject : searchResults) {
            handler.handle(connectorObject);
        }
    }

    @Override
    public void sync(final ObjectClass oclass, final SyncToken token, final SyncResultsHandler handler,
            final OperationOptions options) {
        final String now = getNowTime();
        final String creationTimestamp = "_idmCreationTimestamp";
        final String updateTimestamp = "_idmUpdateTimestamp";
        final String value = token.getValue().toString();

        final FilterBuilder filter = orFilter(rangeFilter(creationTimestamp).gte(value),
                rangeFilter(updateTimestamp).gte(value));

        final List<ConnectorObject> searchResults = doSearch(oclass, filter, options);
        for (ConnectorObject connectorObject : searchResults) {
            final SyncDeltaBuilder syncDeltaBuilder = new SyncDeltaBuilder();
            syncDeltaBuilder.setToken(new SyncToken(now));
            syncDeltaBuilder.setDeltaType(SyncDeltaType.CREATE_OR_UPDATE);
            syncDeltaBuilder.setUid(connectorObject.getUid());
            syncDeltaBuilder.setObject(connectorObject);

            handler.handle(syncDeltaBuilder.build());
        }
    }

    @Override
    public SyncToken getLatestSyncToken(ObjectClass objClass) {
        return new SyncToken(getNowTime());
    }

    private List<ConnectorObject> doSearch(ObjectClass oclass, FilterBuilder filter, OperationOptions options) {
        final List<ConnectorObject> results = new ArrayList<>();

        final TimeValue keepAlive = TimeValue.timeValueMinutes(2);
        final SearchResponse response = client().prepareSearch(configuration.getIndex())
                .setSource(new SearchSourceBuilder().query(filteredQuery(null, filter)).buildAsBytes()).setSize(100)
                .setExplain(true).setScroll(keepAlive).get();

        SearchResponse scrollResponse = response;
        final List<SearchHit> hits = new ArrayList<>();
        while (scrollResponse.getHits().hits().length > 0) {
            hits.addAll(asList(scrollResponse.getHits().hits()));
            scrollResponse = client().prepareSearchScroll(response.getScrollId()).setScroll(keepAlive).get();
        }

        for (SearchHit hit : hits) {
            final ConnectorObjectBuilder cobld = new ConnectorObjectBuilder();
            cobld.setUid(hit.getId());
            for (Entry<String, Object> entry : hit.getSource().entrySet()) {
                final String attrName = entry.getKey();
                final Object attrValue = entry.getValue();

                if (!attrName.equalsIgnoreCase("_id") && !attrName.equalsIgnoreCase("_rev")) {
                    if (attrValue instanceof Collection) {
                        cobld.addAttribute(AttributeBuilder.build(attrName, (Collection) attrValue));
                    } else if (attrValue != null) {
                        cobld.addAttribute(AttributeBuilder.build(attrName, attrValue));
                    } else {
                        cobld.addAttribute(AttributeBuilder.build(attrName));
                    }
                }
            }
            cobld.setObjectClass(oclass);
            results.add(cobld.build());
        }
        return results;
    }

    private byte[] attributesToBytes(Set<Attribute> attributes) {
        try {
            final ObjectMapper mapper = new ObjectMapper();
            final ObjectNode objectNode = mapper.createObjectNode();
            for (Attribute attr : attributes) {
                final List<Object> value = attr.getValue();
                objectNode.putPOJO(attr.getName(),
                        value != null && value.size() > 1 ? value : AttributeUtil.getSingleValue(attr));
            }
            return mapper.writeValueAsBytes(objectNode);
        } catch (JsonProcessingException e) {
            logger.error(e, null);
            return new byte[0];
        }
    }

    private String getNowTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        return sdf.format(new Date());
    }

    private Client client() {
        return connection.client();
    }

    @Override
    public void test() {
    }
}