com.evolveum.midpoint.task.quartzimpl.work.segmentation.StringWorkSegmentationStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.task.quartzimpl.work.segmentation.StringWorkSegmentationStrategy.java

Source

/*
 * Copyright (c) 2010-2018 Evolveum
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.evolveum.midpoint.task.quartzimpl.work.segmentation;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.schema.util.TaskWorkStateTypeUtil;
import com.evolveum.midpoint.task.quartzimpl.work.BaseWorkSegmentationStrategy;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.evolveum.midpoint.xml.ns._public.common.common_3.StringWorkBucketsBoundaryMarkingType.INTERVAL;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.StringWorkBucketsBoundaryMarkingType.PREFIX;
import static java.util.Collections.singletonList;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;

/**
 * @author mederly
 */
public class StringWorkSegmentationStrategy extends BaseWorkSegmentationStrategy {

    private static final Trace LOGGER = TraceManager.getTrace(StringWorkSegmentationStrategy.class);

    @NotNull
    private final StringWorkSegmentationType bucketsConfiguration;
    @NotNull
    private final StringWorkBucketsBoundaryMarkingType marking;
    @NotNull
    private final List<String> boundaries;

    private static final String OID_BOUNDARIES = "0-9a-f";

    public StringWorkSegmentationStrategy(@NotNull TaskWorkManagementType configuration,
            PrismContext prismContext) {
        super(configuration, prismContext);
        this.bucketsConfiguration = (StringWorkSegmentationType) TaskWorkStateTypeUtil
                .getWorkSegmentationConfiguration(configuration);
        this.marking = defaultIfNull(bucketsConfiguration.getComparisonMethod(), INTERVAL);
        this.boundaries = processBoundaries();
    }

    @Override
    protected AbstractWorkBucketContentType createAdditionalBucket(AbstractWorkBucketContentType lastBucketContent,
            Integer lastBucketSequentialNumber) {
        if (marking == INTERVAL) {
            return createAdditionalIntervalBucket(lastBucketContent, lastBucketSequentialNumber);
        } else if (marking == PREFIX) {
            return createAdditionalPrefixBucket(lastBucketContent, lastBucketSequentialNumber);
        } else {
            throw new AssertionError("unsupported marking: " + marking);
        }
    }

    private AbstractWorkBucketContentType createAdditionalIntervalBucket(
            AbstractWorkBucketContentType lastBucketContent, Integer lastBucketSequentialNumber) {
        String lastBoundary;
        if (lastBucketSequentialNumber != null) {
            if (!(lastBucketContent instanceof StringIntervalWorkBucketContentType)) {
                throw new IllegalStateException("Null or unsupported bucket content: " + lastBucketContent);
            }
            StringIntervalWorkBucketContentType lastContent = (StringIntervalWorkBucketContentType) lastBucketContent;
            if (lastContent.getTo() == null) {
                return null;
            }
            lastBoundary = lastContent.getTo();
        } else {
            lastBoundary = null;
        }
        return new StringIntervalWorkBucketContentType().from(lastBoundary).to(computeNextBoundary(lastBoundary));
    }

    private AbstractWorkBucketContentType createAdditionalPrefixBucket(
            AbstractWorkBucketContentType lastBucketContent, Integer lastBucketSequentialNumber) {
        String lastBoundary;
        if (lastBucketSequentialNumber != null) {
            if (!(lastBucketContent instanceof StringPrefixWorkBucketContentType)) {
                throw new IllegalStateException("Null or unsupported bucket content: " + lastBucketContent);
            }
            StringPrefixWorkBucketContentType lastContent = (StringPrefixWorkBucketContentType) lastBucketContent;
            if (lastContent.getPrefix().size() > 1) {
                throw new IllegalStateException("Multiple prefixes are not supported now: " + lastContent);
            } else if (lastContent.getPrefix().isEmpty()) {
                return null;
            } else {
                lastBoundary = lastContent.getPrefix().get(0);
            }
        } else {
            lastBoundary = null;
        }
        String nextBoundary = computeNextBoundary(lastBoundary);
        if (nextBoundary != null) {
            return new StringPrefixWorkBucketContentType().prefix(nextBoundary);
        } else {
            return null;
        }
    }

    private String computeNextBoundary(String lastBoundary) {
        List<Integer> currentIndices = stringToIndices(lastBoundary);
        if (incrementIndices(currentIndices)) {
            return indicesToString(currentIndices);
        } else {
            return null;
        }
    }

    @NotNull
    private List<Integer> stringToIndices(String lastBoundary) {
        List<Integer> currentIndices = new ArrayList<>();
        if (lastBoundary == null) {
            for (int i = 0; i < boundaries.size(); i++) {
                if (i < boundaries.size() - 1) {
                    currentIndices.add(0);
                } else {
                    currentIndices.add(-1);
                }
            }
        } else {
            if (lastBoundary.length() != boundaries.size()) {
                throw new IllegalStateException("Unexpected length of lastBoundary ('" + lastBoundary + "'): "
                        + lastBoundary.length() + ", expected " + boundaries.size());
            }
            for (int i = 0; i < lastBoundary.length(); i++) {
                int index = boundaries.get(i).indexOf(lastBoundary.charAt(i));
                if (index < 0) {
                    throw new IllegalStateException(
                            "Illegal character at position " + (i + 1) + " of lastBoundary (" + lastBoundary
                                    + "): expected one of '" + boundaries.get(i) + "'");
                }
                currentIndices.add(index);
            }
        }
        return currentIndices;
    }

    // true if the new state is a valid one
    private boolean incrementIndices(List<Integer> currentIndices) {
        assert boundaries.size() == currentIndices.size();

        for (int i = currentIndices.size() - 1; i >= 0; i--) {
            int nextValue = currentIndices.get(i) + 1;
            if (nextValue < boundaries.get(i).length()) {
                currentIndices.set(i, nextValue);
                return true;
            } else {
                currentIndices.set(i, 0);
            }
        }
        return false;
    }

    private String indicesToString(List<Integer> currentIndices) {
        assert boundaries.size() == currentIndices.size();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < currentIndices.size(); i++) {
            sb.append(boundaries.get(i).charAt(currentIndices.get(i)));
        }
        return sb.toString();
    }

    @Override
    public Integer estimateNumberOfBuckets(@Nullable TaskWorkStateType workState) {
        int combinations = 1;
        for (String boundary : boundaries) {
            combinations *= boundary.length();
        }
        return marking == INTERVAL ? combinations + 1 : combinations;
    }

    private List<String> processBoundaries() {
        List<String> configuredBoundaries;
        if (bucketsConfiguration instanceof OidWorkSegmentationType
                && bucketsConfiguration.getBoundaryCharacters().isEmpty()) {
            configuredBoundaries = singletonList(OID_BOUNDARIES);
        } else {
            configuredBoundaries = bucketsConfiguration.getBoundaryCharacters();
        }
        int depth = defaultIfNull(bucketsConfiguration.getDepth(), 1);
        List<String> expanded = configuredBoundaries.stream().map(this::expand).collect(Collectors.toList());
        List<String> rv = new ArrayList<>(expanded.size() * depth);
        for (int i = 0; i < depth; i++) {
            rv.addAll(expanded);
        }
        return rv;
    }

    private static class Scanner {
        final String string;
        int index;

        Scanner(String string) {
            this.string = string;
        }

        boolean hasNext() {
            return index < string.length();
        }

        // @pre hasNext()
        boolean isDash() {
            return string.charAt(index) == '-';
        }

        // @pre hasNext()
        public char next() {
            char c = string.charAt(index++);
            if (c != '\\') {
                return c;
            } else if (index != string.length()) {
                return string.charAt(index++);
            } else {
                throw new IllegalArgumentException("Boundary specification cannot end with '\\': " + string);
            }
        }
    }

    private String expand(String s) {
        StringBuilder sb = new StringBuilder();
        Scanner scanner = new Scanner(s);

        while (scanner.hasNext()) {
            if (scanner.isDash()) {
                if (sb.length() == 0) {
                    throw new IllegalArgumentException("Boundary specification cannot start with '-': " + s);
                } else {
                    scanner.next();
                    if (!scanner.hasNext()) {
                        throw new IllegalArgumentException("Boundary specification cannot end with '-': " + s);
                    } else {
                        appendFromTo(sb, sb.charAt(sb.length() - 1), scanner.next());
                    }
                }
            } else {
                sb.append(scanner.next());
            }
        }
        String expanded = sb.toString();
        if (marking == INTERVAL) {
            checkBoundary(expanded);
        }
        return expanded;
    }

    // this is a bit tricky: we do not know what matching rule will be used to execute the comparisons
    // but let's assume it will be consistent with the default ordering
    private void checkBoundary(String boundary) {
        for (int i = 1; i < boundary.length(); i++) {
            char before = boundary.charAt(i - 1);
            char after = boundary.charAt(i);
            if (before >= after) {
                LOGGER.warn("Boundary characters are not sorted in ascending order ({}); comparing '{}' and '{}'",
                        boundary, before, after);
            }
        }
    }

    private void appendFromTo(StringBuilder sb, char fromExclusive, char toInclusive) {
        for (char c = (char) (fromExclusive + 1); c <= toInclusive; c++) {
            sb.append(c);
        }
    }

    // just for testing
    @NotNull
    public List<String> getBoundaries() {
        return boundaries;
    }
}