com.redhat.lightblue.crud.ldap.LdapCRUDController.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.lightblue.crud.ldap.LdapCRUDController.java

Source

/*
 Copyright 2014 Red Hat, Inc. and/or its affiliates.
    
 This file is part of lightblue.
    
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
    
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.redhat.lightblue.crud.ldap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.redhat.lightblue.common.ldap.DBResolver;
import com.redhat.lightblue.common.ldap.LdapConstant;
import com.redhat.lightblue.common.ldap.LdapDataStore;
import com.redhat.lightblue.common.ldap.LdapFieldNameTranslator;
import com.redhat.lightblue.common.ldap.LightblueUtil;
import com.redhat.lightblue.crud.CRUDController;
import com.redhat.lightblue.crud.CRUDDeleteResponse;
import com.redhat.lightblue.crud.CRUDFindResponse;
import com.redhat.lightblue.crud.CRUDInsertionResponse;
import com.redhat.lightblue.crud.CRUDOperationContext;
import com.redhat.lightblue.crud.CRUDSaveResponse;
import com.redhat.lightblue.crud.CRUDUpdateResponse;
import com.redhat.lightblue.crud.CrudConstants;
import com.redhat.lightblue.crud.DocCtx;
import com.redhat.lightblue.crud.ldap.translator.ResultTranslator;
import com.redhat.lightblue.crud.ldap.translator.SortTranslator;
import com.redhat.lightblue.eval.FieldAccessRoleEvaluator;
import com.redhat.lightblue.eval.Projector;
import com.redhat.lightblue.hystrix.ldap.InsertCommand;
import com.redhat.lightblue.metadata.EntityMetadata;
import com.redhat.lightblue.metadata.FieldCursor;
import com.redhat.lightblue.metadata.MetadataConstants;
import com.redhat.lightblue.metadata.MetadataListener;
import com.redhat.lightblue.metadata.types.StringType;
import com.redhat.lightblue.query.Projection;
import com.redhat.lightblue.query.QueryExpression;
import com.redhat.lightblue.query.Sort;
import com.redhat.lightblue.query.UpdateExpression;
import com.redhat.lightblue.util.Error;
import com.redhat.lightblue.util.JsonDoc;
import com.redhat.lightblue.util.Path;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl;
import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl;

/**
 * {@link CRUDController} implementation for LDAP.
 *
 * @author dcrissman
 */
public class LdapCRUDController implements CRUDController {

    private final DBResolver dbResolver;

    public LdapCRUDController(DBResolver dbResolver) {
        this.dbResolver = dbResolver;
    }

    @Override
    public CRUDInsertionResponse insert(CRUDOperationContext ctx, Projection projection) {
        CRUDInsertionResponse response = new CRUDInsertionResponse();
        response.setNumInserted(0);

        List<DocCtx> documents = ctx.getDocumentsWithoutErrors();
        if (documents == null || documents.isEmpty()) {
            return response;
        }

        EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
        LdapDataStore store = LdapCrudUtil.getLdapDataStore(md);
        LdapFieldNameTranslator fieldNameTranslator = LdapCrudUtil.getLdapFieldNameTranslator(md);

        FieldAccessRoleEvaluator roles = new FieldAccessRoleEvaluator(md, ctx.getCallerRoles());
        EntryBuilder entryBuilder = new EntryBuilder(md, fieldNameTranslator);

        //Create Entry instances for each document.
        List<com.unboundid.ldap.sdk.Entry> entries = new ArrayList<com.unboundid.ldap.sdk.Entry>();
        Map<DocCtx, String> documentToDnMap = new HashMap<DocCtx, String>();
        boolean hasError = false;
        for (DocCtx document : documents) {
            List<Path> paths = roles.getInaccessibleFields_Insert(document);
            if ((paths != null) && !paths.isEmpty()) {
                for (Path path : paths) {
                    document.addError(
                            Error.get("insert", CrudConstants.ERR_NO_FIELD_INSERT_ACCESS, path.toString()));
                    continue;
                }
            }

            Path uniqueFieldPath = fieldNameTranslator.translateAttributeName(store.getUniqueAttribute());
            JsonNode uniqueNode = document.get(uniqueFieldPath);
            if (uniqueNode == null) {
                throw Error.get(MetadataConstants.ERR_PARSE_MISSING_ELEMENT, store.getUniqueAttribute());
            }

            String dn = LdapCrudUtil.createDN(store, uniqueNode.asText());
            documentToDnMap.put(document, dn);
            try {
                entries.add(entryBuilder.build(dn, document));
            } catch (Exception e) {
                document.addError(Error.get(e));
                hasError = true;
            }
        }
        if (hasError) {
            return response;
        }

        //Persist each Entry.
        LDAPConnection connection = getNewLdapConnection(store);
        for (com.unboundid.ldap.sdk.Entry entry : entries) {
            InsertCommand command = new InsertCommand(connection, entry);

            LDAPResult result = command.execute();
            if (result.getResultCode() != ResultCode.SUCCESS) {
                //TODO: Do something to indicate unsuccessful status
                continue;
            }

            response.setNumInserted(response.getNumInserted() + 1);
        }

        projectChanges(projection, ctx, documentToDnMap);

        return response;
    }

    @Override
    public CRUDSaveResponse save(CRUDOperationContext ctx, boolean upsert, Projection projection) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public CRUDUpdateResponse update(CRUDOperationContext ctx, QueryExpression query, UpdateExpression update,
            Projection projection) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public CRUDDeleteResponse delete(CRUDOperationContext ctx, QueryExpression query) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public CRUDFindResponse find(CRUDOperationContext ctx, QueryExpression query, Projection projection, Sort sort,
            Long from, Long to) {

        if (query == null) {
            throw new IllegalArgumentException("No query was provided.");
        }
        if (projection == null) {
            throw new IllegalArgumentException("No projection was provided");
        }

        EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
        LdapDataStore store = LdapCrudUtil.getLdapDataStore(md);

        CRUDFindResponse response = new CRUDFindResponse();
        response.setSize(0);

        LDAPConnection connection = getNewLdapConnection(store);

        LdapFieldNameTranslator fieldNameTranslator = LdapCrudUtil.getLdapFieldNameTranslator(md);

        try {
            //TODO: Support scopes other than SUB
            SearchRequest request = new SearchRequest(store.getBaseDN(), SearchScope.SUB,
                    new FilterBuilder(fieldNameTranslator).build(query),
                    translateFieldNames(fieldNameTranslator, gatherRequiredFields(md, projection, query, sort))
                            .toArray(new String[0]));
            if (sort != null) {
                request.addControl(new ServerSideSortRequestControl(false,
                        new SortTranslator(fieldNameTranslator).translate(sort)));
            }
            if ((from != null) && (from > 0)) {
                int endPos = to.intValue() - from.intValue();
                request.addControl(new VirtualListViewRequestControl(from.intValue(), 0, endPos, 0, null, false));
            }

            SearchResult result = connection.search(request);

            response.setSize(result.getEntryCount());
            ResultTranslator resultTranslator = new ResultTranslator(ctx.getFactory().getNodeFactory(), md,
                    fieldNameTranslator);
            List<DocCtx> translatedDocs = new ArrayList<DocCtx>();
            for (SearchResultEntry entry : result.getSearchEntries()) {
                try {
                    translatedDocs.add(resultTranslator.translate(entry));
                } catch (Exception e) {
                    ctx.addError(Error.get(e));
                }
            }
            ctx.setDocuments(translatedDocs);

            Projector projector = Projector
                    .getInstance(
                            Projection
                                    .add(projection,
                                            new FieldAccessRoleEvaluator(md, ctx.getCallerRoles())
                                                    .getExcludedFields(FieldAccessRoleEvaluator.Operation.find)),
                            md);
            for (DocCtx document : ctx.getDocumentsWithoutErrors()) {
                document.setOutputDocument(projector.project(document, ctx.getFactory().getNodeFactory()));
            }
        } catch (LDAPException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return response;
    }

    @Override
    public void updatePredefinedFields(CRUDOperationContext ctx, JsonDoc doc) {
        //Do Nothing!!
    }

    @Override
    public MetadataListener getMetadataListener() {
        return new LdapMetadataListener();
    }

    /**
     * Returns a list of the field names that are needed for the operation to be
     * successful.
     * @param md - {@link EntityMetadata}.
     * @param projection - (optional) {@link Projection}.
     * @param query - (optional) {@link QueryExpression}.
     * @param sort - (optional) {@link Sort}.
     * @return list of field names.
     */
    private Set<Path> gatherRequiredFields(EntityMetadata md, Projection projection, QueryExpression query,
            Sort sort) {
        Set<Path> paths = new HashSet<Path>();

        FieldCursor cursor = md.getFieldCursor();
        while (cursor.next()) {
            Path node = cursor.getCurrentPath();
            String fieldName = node.getLast();

            if (((projection != null) && projection.isFieldRequiredToEvaluateProjection(node))
                    || ((query != null) && query.isRequired(node)) || ((sort != null) && sort.isRequired(node))) {
                if (LightblueUtil.isFieldAnArrayCount(fieldName, md.getFields())) {
                    /*
                     * Handles the case of an array count field, which will not actually exist in
                     * the ldap entity.
                     */
                    paths.add(node.mutableCopy()
                            .setLast(LightblueUtil.createArrayFieldNameFromCountField(fieldName)).immutableCopy());
                } else {
                    paths.add(node);
                }
            }
        }

        return paths;
    }

    /**
     * Translates a <code>Collection</code> of fieldNames into a <code>Set</code> of
     * attributeNames
     * @param property - {@link LdapFieldNameTranslator}.
     * @param fieldNames - <code>Collection</code> of fieldNames to translated
     * @return <code>Set</code> of translated attributeNames.
     */
    private Set<String> translateFieldNames(LdapFieldNameTranslator property, Collection<Path> fieldNames) {
        Set<String> attributes = new HashSet<String>();
        for (Path path : fieldNames) {
            attributes.add(property.translateFieldName(path));
        }

        return attributes;
    }

    /**
     * For Insert and Save (and possibly Update), this method will project the results back
     * onto the documents.
     * @param projection - {@link Projection} If null, then nothing will happen.
     * @param ctx - {@link CRUDOperationContext}
     * @param documentToDnMap - Map linking {@link DocCtx} to the DN that represents it.
     */
    private void projectChanges(Projection projection, CRUDOperationContext ctx,
            Map<DocCtx, String> documentToDnMap) {
        if (projection == null) {
            return;
        }

        EntityMetadata md = ctx.getEntityMetadata(ctx.getEntityName());
        JsonNodeFactory factory = ctx.getFactory().getNodeFactory();
        LdapFieldNameTranslator fieldNameTranslator = LdapCrudUtil.getLdapFieldNameTranslator(md);

        Set<String> requiredAttributeNames = translateFieldNames(fieldNameTranslator,
                gatherRequiredFields(md, projection, null, null));
        Projector projector = Projector
                .getInstance(Projection.add(projection, new FieldAccessRoleEvaluator(md, ctx.getCallerRoles())
                        .getExcludedFields(FieldAccessRoleEvaluator.Operation.insert)), md);

        Path dnFieldPath = fieldNameTranslator.translateAttributeName(LdapConstant.ATTRIBUTE_DN);

        for (Entry<DocCtx, String> insertedDn : documentToDnMap.entrySet()) {
            DocCtx document = insertedDn.getKey();
            String dn = insertedDn.getValue();
            DocCtx projectionResponseJson = null;

            // If only dn is in the projection, then no need to query LDAP.
            if ((requiredAttributeNames.size() == 1)
                    && requiredAttributeNames.contains(LdapConstant.ATTRIBUTE_DN)) {
                JsonDoc jdoc = new JsonDoc(factory.objectNode());
                jdoc.modify(dnFieldPath, StringType.TYPE.toJson(factory, dn), true);
                projectionResponseJson = new DocCtx(jdoc);
            }
            //TODO: else fetch entity from LDAP and project results.
            //TODO: Probably want to batch fetch as opposed to individual fetches.

            document.setOutputDocument(projector.project(projectionResponseJson, factory));
        }
    }

    /**
     * Returns a new connection to ldap.
     * @param store - {@link LdapDataStore} to connect too.
     * @return a new connection to ldap
     * @throws RuntimeException when unable to connect to ldap.
     */
    private LDAPConnection getNewLdapConnection(LdapDataStore store) {
        LDAPConnection connection = null;
        try {
            connection = dbResolver.get(store);
        } catch (LDAPException e) {
            //TODO: throw more relevant exception.
            throw new RuntimeException("Unable to establish connection to LDAP", e);
        }
        return connection;
    }

}