com.jivesoftware.os.tasmo.view.reader.service.ViewProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.jivesoftware.os.tasmo.view.reader.service.ViewProvider.java

Source

/*
 * $Revision$
 * $Date$
 *
 * Copyright (C) 1999-$year$ Jive Software. All rights reserved.
 *
 * This software is the proprietary information of Jive Software. Use is subject to license terms.
 */
package com.jivesoftware.os.tasmo.view.reader.service;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.jivesoftware.os.jive.utils.logger.MetricLogger;
import com.jivesoftware.os.jive.utils.logger.MetricLoggerFactory;
import com.jivesoftware.os.jive.utils.row.column.value.store.api.ColumnValueAndTimestamp;
import com.jivesoftware.os.tasmo.configuration.views.PathAndDictionary;
import com.jivesoftware.os.tasmo.configuration.views.TenantViewsProvider;
import com.jivesoftware.os.tasmo.id.Id;
import com.jivesoftware.os.tasmo.id.ImmutableByteArray;
import com.jivesoftware.os.tasmo.id.TenantId;
import com.jivesoftware.os.tasmo.id.TenantIdAndCentricId;
import com.jivesoftware.os.tasmo.model.path.ModelPath;
import com.jivesoftware.os.tasmo.view.reader.api.ViewDescriptor;
import com.jivesoftware.os.tasmo.view.reader.api.ViewReader;
import com.jivesoftware.os.tasmo.view.reader.api.ViewResponse;
import com.jivesoftware.os.tasmo.view.reader.service.shared.ViewValue;
import com.jivesoftware.os.tasmo.view.reader.service.shared.ViewValueStore.ViewCollector;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 *
 */
public class ViewProvider<V> implements ViewReader<V> {

    private static final String VIEW_READ_LATENCY = "view>read>latency";
    private static final String VIEW_PERMISSIONS_LATENCY = "view>permissions>latency";
    private static final String VIEW_MERGE_LATENCY = "view>merge>latency";
    private static final String VIEW_READ_VIEW_COUNT = "view>read>view>count";
    private static final String VIEW_READ_CALL_COUNT = "view>read>call>count";

    private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
    private final ViewPermissionChecker viewPermissionChecker;
    private final ViewValueReader viewValueReader;
    private final TenantViewsProvider tenantViewsProvider;
    private final ViewFormatter<V> viewFormatter;
    private final JsonViewMerger merger;
    private final StaleViewFieldStream staleViewFieldStream;
    private final long viewMaxSizeInBytes; // This is the number of bytes that can be read for a single view. Defends against posion view that could cause OOM

    public ViewProvider(ViewPermissionChecker viewPermissionChecker, ViewValueReader viewValueReader,
            TenantViewsProvider viewModel, ViewFormatter<V> viewFormatter, JsonViewMerger merger,
            StaleViewFieldStream staleViewFieldStream, long viewMaxSizeInBytes) {
        this.viewPermissionChecker = viewPermissionChecker;
        this.viewValueReader = viewValueReader;
        this.tenantViewsProvider = viewModel;
        this.viewFormatter = viewFormatter;
        this.merger = merger;
        if (staleViewFieldStream == null) {
            throw new IllegalStateException("deadFieldStateStream cannot be null.");
        }
        this.staleViewFieldStream = staleViewFieldStream;
        this.viewMaxSizeInBytes = viewMaxSizeInBytes;
    }

    @Override
    public V readView(ViewDescriptor request) throws IOException {
        List<V> views = readViews(ImmutableList.of(request));
        return views.isEmpty() ? viewFormatter.emptyView() : views.get(0);
    }

    @Override
    public List<V> readViews(List<ViewDescriptor> request) throws IOException {
        LOG.inc(VIEW_READ_CALL_COUNT);
        Preconditions.checkArgument(request != null);
        Map<TenantAndActor, Set<Id>> permisionCheckTheseIds = new HashMap<>();
        List<ViewCollectorImpl<V>> viewCollectors = buildViewCollectors(request, permisionCheckTheseIds);

        LOG.startTimer(VIEW_READ_LATENCY);
        try {
            viewValueReader.readViewValues(viewCollectors);
        } finally {
            LOG.stopTimer(VIEW_READ_LATENCY);
        }

        Map<TenantAndActor, Set<Id>> canViewTheseIds;
        LOG.startTimer(VIEW_PERMISSIONS_LATENCY);
        try {
            canViewTheseIds = checkPermissions(permisionCheckTheseIds);
        } finally {
            LOG.stopTimer(VIEW_PERMISSIONS_LATENCY);
        }

        LOG.startTimer(VIEW_MERGE_LATENCY);
        try {
            List<V> views = collectViewObject(viewCollectors, canViewTheseIds);
            LOG.inc(VIEW_READ_VIEW_COUNT, views.size());
            return views;
        } catch (Exception ex) {
            LOG.error("Failed while loading {}", request);
            throw new IOException("Failed to load for the following reason.", ex);
        } finally {
            LOG.stopTimer(VIEW_MERGE_LATENCY);
        }
    }

    List<ViewCollectorImpl<V>> buildViewCollectors(List<ViewDescriptor> request,
            Map<TenantAndActor, Set<Id>> permisionCheckTheseIds) {
        List<ViewCollectorImpl<V>> viewCollectors = Lists.newArrayList();
        for (ViewDescriptor viewDescriptor : request) {
            TenantAndActor tenantAndActor = new TenantAndActor(viewDescriptor.getTenantId(),
                    viewDescriptor.getActorId());
            Set<Id> accumulateIdsToBePermissionsChecked = permisionCheckTheseIds.get(tenantAndActor);
            if (accumulateIdsToBePermissionsChecked == null) {
                accumulateIdsToBePermissionsChecked = new HashSet<>();
                permisionCheckTheseIds.put(tenantAndActor, accumulateIdsToBePermissionsChecked);
            }

            Map<Integer, PathAndDictionary> viewFieldBindings = tenantViewsProvider.getViewFieldBinding(
                    viewDescriptor.getTenantIdAndCentricId().getTenantId(),
                    viewDescriptor.getViewId().getClassName());

            // TODO: be able to pass back error result per descriptor to front end
            if (viewFieldBindings == null) {
                LOG.error(viewDescriptor.getViewId().getClassName() + " has no declared view bindings.");
            }

            ViewFieldsCollector viewFieldsCollector = new ViewFieldsCollector(merger, viewMaxSizeInBytes);
            viewCollectors.add(new ViewCollectorImpl<>(viewDescriptor, viewFieldBindings, viewFieldsCollector,
                    staleViewFieldStream, accumulateIdsToBePermissionsChecked, viewFormatter));
        }
        return viewCollectors;
    }

    List<V> collectViewObject(List<ViewCollectorImpl<V>> viewCollectors,
            Map<TenantAndActor, Set<Id>> canViewTheseIds) throws Exception {
        List<V> result = new ArrayList<>();
        for (ViewCollectorImpl<V> viewCollector : viewCollectors) {
            ViewDescriptor viewDescriptor = viewCollector.getViewDescriptor();
            TenantAndActor tenantAndActor = new TenantAndActor(viewDescriptor.getTenantId(),
                    viewDescriptor.getActorId());
            result.add(viewCollector.getView(canViewTheseIds.get(tenantAndActor)));
        }
        return result;
    }

    Map<TenantAndActor, Set<Id>> checkPermissions(Map<TenantAndActor, Set<Id>> permissionCheckTheseIds) {
        Map<TenantAndActor, Set<Id>> canViewTheseIds = new HashMap<>();
        for (TenantAndActor tenantAndActor : permissionCheckTheseIds.keySet()) { // 1 permissions check call per tenant and actor id tuple.
            Collection<Id> ids = permissionCheckTheseIds.get(tenantAndActor);
            ViewPermissionCheckResult checkResult = viewPermissionChecker.check(tenantAndActor.tenantId,
                    tenantAndActor.actorId, new HashSet<>(ids));
            canViewTheseIds.put(tenantAndActor, Sets.union(checkResult.allowed(), checkResult.unknown())); // For now... TODO
        }
        return canViewTheseIds;
    }

    static class ViewCollectorImpl<VV> implements ViewCollector {

        private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
        private final ViewDescriptor viewDescriptor;
        private final Map<Integer, PathAndDictionary> viewClassFieldBindings;
        private final ViewFieldsCollector viewFieldsCollector;
        private final StaleViewFieldStream staleViewFieldStream;
        private final Set<Id> permissionCheckTheseIds;
        private final ViewFormatter<VV> viewFormatter;

        public ViewCollectorImpl(ViewDescriptor viewDescriptor,
                Map<Integer, PathAndDictionary> viewClassFieldBindings, ViewFieldsCollector viewFieldsCollector,
                StaleViewFieldStream staleViewFieldStream, Set<Id> permissionCheckTheseIds,
                ViewFormatter<VV> viewFormatter) {
            this.viewDescriptor = viewDescriptor;
            this.viewClassFieldBindings = viewClassFieldBindings;
            this.viewFieldsCollector = viewFieldsCollector;
            this.staleViewFieldStream = staleViewFieldStream;
            this.permissionCheckTheseIds = permissionCheckTheseIds;
            this.viewFormatter = viewFormatter;
        }

        public VV getView(Set<Id> canViewTheseIds) throws Exception {
            ViewResponse view = viewFieldsCollector.getView(canViewTheseIds);
            TenantIdAndCentricId tenantIdAndCentricId = viewDescriptor.getTenantIdAndCentricId();
            return viewFormatter.getView(tenantIdAndCentricId, viewDescriptor.getViewId(), view);
        }

        @Override
        public ViewDescriptor getViewDescriptor() {
            return viewDescriptor;
        }

        @Override
        public ColumnValueAndTimestamp<ImmutableByteArray, ViewValue, Long> callback(
                ColumnValueAndTimestamp<ImmutableByteArray, ViewValue, Long> fieldValue) throws Exception {
            if (viewClassFieldBindings == null) { // if factoed out so that we dont exceed 4 levels of if nesting.
                return fieldValue;
            }
            if (fieldValue != null) {
                ByteBuffer bb = ByteBuffer.wrap(fieldValue.getColumn().getImmutableBytes());
                Integer modelPathIdHashCode = bb.getInt();
                PathAndDictionary pathAndDictionary = viewClassFieldBindings.get(modelPathIdHashCode);

                if (pathAndDictionary != null) {
                    ModelPath modelPath = pathAndDictionary.getPath();
                    if (modelPath != null) {
                        int pathComboKey = bb.getInt();
                        String[] viewPathClasses = pathAndDictionary.getDictionary()
                                .lookupModelPathClasses(pathComboKey);

                        if (viewPathClasses != null && viewPathClasses.length > 0) {
                            Id[] modelPathIds = modelPathIds(bb, modelPath.getPathMemberSize());
                            if (viewFieldsCollector.add(viewDescriptor, modelPath, modelPathIds, viewPathClasses,
                                    fieldValue.getValue(), fieldValue.getTimestamp())) {
                                permissionCheckTheseIds.addAll(Arrays.asList(modelPathIds));
                            }
                        } else {
                            LOG.warn(
                                    "Unable to look up model path classes for view path with path combination key: "
                                            + pathComboKey);
                        }
                    } else {
                        LOG.warn("failed to load ViewValueBinding for viewValueBindingKey={}, fieldValue={} ",
                                new Object[] { modelPathIdHashCode, fieldValue });
                    }
                } else {
                    LOG.debug(
                            "Failed to load model path and view path dictionary from column key. Older column key format is likely the case.");
                    try {
                        staleViewFieldStream.stream(viewDescriptor, fieldValue);
                    } catch (Exception x) {
                        LOG.error(
                                "Implementer of staleViewFieldStream is failing to handle all exception appropriately. ",
                                x);
                    }
                }
            } else {
                viewFieldsCollector.done(); //eos
            }
            return fieldValue;
        }

        private Id[] modelPathIds(ByteBuffer bb, int count) {
            Id[] ids = new Id[count];
            for (int i = 0; i < count; i++) {
                int l = bb.get();
                byte[] idBytes = new byte[l];
                bb.get(idBytes);
                ids[i] = new Id(idBytes);
            }
            return ids;
        }

    }

    static class TenantAndActor {

        private final TenantId tenantId;
        private final Id actorId;

        TenantAndActor(TenantId tenantId, Id actorId) {
            this.tenantId = tenantId;
            this.actorId = actorId;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 79 * hash + Objects.hashCode(this.tenantId);
            hash = 79 * hash + Objects.hashCode(this.actorId);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final TenantAndActor other = (TenantAndActor) obj;
            if (!Objects.equals(this.tenantId, other.tenantId)) {
                return false;
            }
            if (!Objects.equals(this.actorId, other.actorId)) {
                return false;
            }
            return true;
        }
    }
}