rapture.kernel.SeriesApiImpl.java Source code

Java tutorial

Introduction

Here is the source code for rapture.kernel.SeriesApiImpl.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2016 Incapture Technologies LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package rapture.kernel;

import static rapture.common.Scheme.SERIES;

import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.antlr.runtime.RecognitionException;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

import rapture.common.CallingContext;
import rapture.common.EntitlementSet;
import rapture.common.Hose;
import rapture.common.Messages;
import rapture.common.RaptureFolderInfo;
import rapture.common.RaptureURI;
import rapture.common.Scheme;
import rapture.common.SeriesDouble;
import rapture.common.SeriesPoint;
import rapture.common.SeriesRepoConfig;
import rapture.common.SeriesRepoConfigStorage;
import rapture.common.SeriesString;
import rapture.common.SeriesValue;
import rapture.common.api.SeriesApi;
import rapture.common.exception.ExceptionToString;
import rapture.common.exception.RaptureException;
import rapture.common.exception.RaptureExceptionFactory;
import rapture.common.impl.jackson.JacksonUtil;
import rapture.common.series.SeriesUpdateObject;
import rapture.common.shared.series.DeleteSeriesPayload;
import rapture.common.shared.series.ListSeriesByUriPrefixPayload;
import rapture.dsl.serfun.HoseArg;
import rapture.dsl.serfun.HoseProgram;
import rapture.dsl.serfun.LoadHose;
import rapture.kernel.context.ContextValidator;
import rapture.kernel.pipeline.SearchPublisher;
import rapture.repo.SeriesRepo;
import rapture.series.config.ConfigValidatorService;
import rapture.series.config.InvalidConfigException;

public class SeriesApiImpl extends KernelBase implements SeriesApi {

    private static Logger log = Logger.getLogger(SeriesApiImpl.class);

    public SeriesApiImpl(Kernel raptureKernel) {
        super(raptureKernel);
    }

    @Override
    public void createSeriesRepo(CallingContext context, String seriesURI, String config) {
        checkParameter("Repository URI", seriesURI);
        checkParameter("Config", config);

        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        String authority = internalURI.getAuthority();
        if ((authority == null) || authority.isEmpty()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("NoAuthority")); //$NON-NLS-1$
        }
        if (internalURI.hasDocPath()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("NoDocPath", seriesURI)); //$NON-NLS-1$
        }
        try {
            ConfigValidatorService.validateConfig(config);
        } catch (InvalidConfigException | RecognitionException e) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("ConfigNotValid", config), e); //$NON-NLS-1$
        }

        if (seriesRepoExists(context, seriesURI)) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("Exists", seriesURI)); //$NON-NLS-1$
        }

        // Repo config
        SeriesRepoConfig series = new SeriesRepoConfig();
        series.setAuthority(internalURI.getAuthority());
        series.setConfig(config);
        SeriesRepoConfigStorage.add(series, context.getUser(), "Create series repo");

        // series repo will be cached when accessed the first time
    }

    @Override
    public Boolean seriesRepoExists(CallingContext context, String seriesURI) {
        RaptureURI uri = new RaptureURI(seriesURI, Scheme.SERIES);
        if (uri.hasDocPath()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("NoDocPath", seriesURI)); //$NON-NLS-1$
            // $NON-NLS-1$
        }
        SeriesRepo series = getRepoFromCache(uri.getAuthority());
        return series != null;
    }

    @Override
    public SeriesRepoConfig getSeriesRepoConfig(CallingContext context, String seriesURI) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);

        if (internalURI.hasDocPath()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("NoDocPath", seriesURI)); //$NON-NLS-1$
        }

        return SeriesRepoConfigStorage.readByAddress(internalURI);
    }

    @Override
    public void deleteSeriesRepo(CallingContext context, String seriesURI) {
        // Need to drop the data, then drop the type definition

        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);

        if (internalURI.hasDocPath()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("NoDocPath", seriesURI)); //$NON-NLS-1$
        }

        SeriesRepo repo = getRepoFromCache(internalURI.getAuthority());
        if (repo != null) {
            repo.drop();
        }
        SearchPublisher.publishDropMessage(context, internalURI.toString());
        SeriesRepoConfigStorage.deleteByAddress(internalURI, context.getUser(),
                Messages.getString("Admin.RemoveType")); //$NON-NLS-1$ //$NON-NLS-2$
        removeRepoFromCache(internalURI.getAuthority());
    }

    @Override
    public void deletePointsFromSeriesByPointKey(CallingContext context, String seriesURI, List<String> columns) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        String docPath = internalURI.getDocPath();
        SeriesRepo repo = getRepoFromCache(internalURI.getAuthority());
        if ((repo != null) && (docPath != null)) {
            repo.deletePointsFromSeriesByColumn(internalURI.getDocPath(), columns);
        }
        if (SearchPublisher.shouldPublish(getConfigFromCache(internalURI.getAuthority()), internalURI)) {
            // Need to delete points from ES.
            // Drop the old series and re-add all the points that remain.
            // TODO Is there a better way? Can we delete individual series points from ES?
            SearchPublisher.publishDeleteMessage(context, getConfigFromCache(internalURI.getAuthority()),
                    internalURI);
            List<SeriesPoint> points = getPoints(context, seriesURI);
            List<String> pointKeys = new ArrayList<>(points.size());
            List<String> pointValues = new ArrayList<>(points.size());
            for (SeriesPoint p : points) {
                pointKeys.add(p.getColumn());
                pointValues.add(p.getValue());
            }
            processSearchUpdate(context, internalURI, pointKeys, pointValues);
        }
    }

    @Override
    public void deletePointsFromSeries(CallingContext context, String seriesURI) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        String docPath = internalURI.getDocPath();
        SeriesRepo repo = getRepoFromCache(internalURI.getAuthority());
        if ((repo != null) && (docPath != null)) {
            repo.deletePointsFromSeries(docPath);
        }
        SearchPublisher.publishDeleteMessage(context, getConfigFromCache(internalURI.getAuthority()), internalURI);
    }

    @Override
    public List<SeriesPoint> getPoints(CallingContext context, String seriesURI) {
        return Lists.transform(getPointsAsSeriesValues(context, seriesURI), sv2xsf);
    }

    private List<SeriesValue> getPointsAsSeriesValues(CallingContext context, String seriesURI) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repo = getRepoOrFail(internalURI);
        List<SeriesValue> raw = repo.getPoints(internalURI.getDocPath());
        return raw;
    }

    private Function<SeriesValue, SeriesPoint> sv2xsf = new Function<SeriesValue, SeriesPoint>() {
        @Override
        public SeriesPoint apply(SeriesValue in) {
            SeriesPoint result = new SeriesPoint();
            result.setColumn(in.getColumn());
            result.setValue(in.isString() ? ("'" + in.asString()) : in.asString());
            return result;
        }
    };

    private Function<SeriesValue, SeriesDouble> sv2sd = new Function<SeriesValue, SeriesDouble>() {
        @Override
        public SeriesDouble apply(SeriesValue in) {
            SeriesDouble result = new SeriesDouble();
            result.setKey(in.getColumn());
            result.setValue(in.asDouble());
            return result;
        }
    };

    private Function<SeriesValue, SeriesString> sv2ss = new Function<SeriesValue, SeriesString>() {
        @Override
        public SeriesString apply(SeriesValue in) {
            SeriesString result = new SeriesString();
            result.setKey(in.getColumn());
            result.setValue(in.asString());
            return result;
        }
    };

    private Function<String, HoseArg> xsf2sv = new Function<String, HoseArg>() {
        @Override
        public HoseArg apply(String in) {
            if (Character.isDigit(in.charAt(0))) {
                return in.contains(".") ? HoseArg.makeDecimal(in) : HoseArg.makeLong(in);
            } else if (in.charAt(0) == '\'') {
                return HoseArg.makeString(in.substring(1));
            } else if (in.charAt(0) == '@') {
                return HoseArg.makeStream(LoadHose.make(in));
            } else {
                throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                        apiMessageCatalog.getMessage("BadHose"));
            }
        }
    };

    @Override
    public SeriesPoint getLastPoint(CallingContext context, String seriesURI) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repo = getRepoOrFail(internalURI);
        SeriesValue lastPoint = repo.getLastPoint(internalURI.getDocPath());
        if (lastPoint == null) {
            return null;
        }
        return sv2xsf.apply(lastPoint);
    }

    @Override
    public List<SeriesPoint> getPointsAfter(CallingContext context, String seriesURI, String startColumn,
            int maxNumber) {
        return Lists.transform(getPointsAfterAsSeriesValues(context, seriesURI, startColumn, maxNumber), sv2xsf);
    }

    @Override
    public List<SeriesPoint> getPointsAfterReverse(CallingContext context, String seriesURI, String startColumn,
            int maxNumber) {
        return Lists.transform(getPointsAfterReverseAsSeriesValues(context, seriesURI, startColumn, maxNumber),
                sv2xsf);
    }

    private List<SeriesValue> getPointsAfterReverseAsSeriesValues(CallingContext context, String seriesURI,
            String startColumn, int maxNumber) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repo = getRepoOrFail(internalURI);
        return repo.getPointsAfterReverse(internalURI.getDocPath(), startColumn, maxNumber);
    }

    private List<SeriesValue> getPointsAfterAsSeriesValues(CallingContext context, String seriesURI,
            String startColumn, int maxNumber) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repo = getRepoOrFail(internalURI);
        return repo.getPointsAfter(internalURI.getDocPath(), startColumn, maxNumber);
    }

    /**
     * Return the {@link SeriesRepo} for this {@link RaptureURI} or throw a {@link RaptureException} with an error message
     *
     * @param uri
     * @return
     */
    private SeriesRepo getRepoOrFail(RaptureURI uri) {
        SeriesRepo repo = getRepoFromCache(uri.getAuthority());
        if (repo == null) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("NoSuchRepo", uri.toAuthString())); //$NON-NLS-1$
        } else {
            return repo;
        }
    }

    @Override
    public List<SeriesPoint> getPointsInRange(CallingContext context, String seriesURI, String startColumn,
            String endColumn, int maxNumber) {
        return Lists.transform(
                getPointsInRangeAsSeriesValues(context, seriesURI, startColumn, endColumn, maxNumber), sv2xsf);
    }

    private List<SeriesValue> getPointsInRangeAsSeriesValues(CallingContext context, String seriesURI,
            String startColumn, String endColumn, int maxNumber) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repo = getRepoOrFail(internalURI);
        return repo.getPointsAfter(internalURI.getDocPath(), startColumn, endColumn, maxNumber);
    }

    @Override
    public void addDoubleToSeries(CallingContext context, String seriesURI, String pointKey, Double pointValue) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addDoubleToSeries(internalURI.getDocPath(), pointKey, pointValue);
        processSearchUpdate(context, internalURI, pointKey, pointValue);
    }

    @Override
    public void addLongToSeries(CallingContext context, String seriesURI, String pointKey, Long pointValue) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addLongToSeries(internalURI.getDocPath(), pointKey, pointValue);
        processSearchUpdate(context, internalURI, pointKey, pointValue);
    }

    @Override
    public void addStringToSeries(CallingContext context, String seriesURI, String pointKey, String pointValue) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addStringToSeries(internalURI.getDocPath(), pointKey, pointValue);
        processSearchUpdate(context, internalURI, pointKey, pointValue);
    }

    @Override
    public void addStructureToSeries(CallingContext context, String seriesURI, String pointKey, String pointValue) {
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addStructureToSeries(internalURI.getDocPath(), pointKey, pointValue);
        processSearchUpdate(context, internalURI, pointKey, pointValue);
    }

    @Override
    public void addDoublesToSeries(CallingContext context, String seriesURI, List<String> pointKeys,
            List<Double> pointValues) {
        checkParameter("URI", seriesURI);
        checkParameter("pointKeys", pointKeys);
        checkParameter("pointValues", pointValues);
        if (pointKeys.size() != pointValues.size()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("ArgSizeNotEqual")); //$NON-NLS-1$
        }
        for (Double v : pointValues) {
            if (v == null)
                throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                        apiMessageCatalog.getMessage("NullEmpty", "pointValue")); //$NON-NLS-1$
        }
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addDoublesToSeries(internalURI.getDocPath(), pointKeys, pointValues);
        processSearchUpdate(context, internalURI, pointKeys, pointValues);
    }

    @Override
    public void addLongsToSeries(CallingContext context, String seriesURI, List<String> pointKeys,
            List<Long> pointValues) {
        checkParameter("URI", seriesURI);
        checkParameter("pointKeys", pointKeys);
        checkParameter("pointValues", pointValues);
        if (pointKeys.size() != pointValues.size()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("ArgSizeNotEqual")); //$NON-NLS-1$
        }
        for (Long v : pointValues) {
            if (v == null)
                throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                        apiMessageCatalog.getMessage("NullEmpty", "pointValue")); //$NON-NLS-1$
        }
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addLongsToSeries(internalURI.getDocPath(), pointKeys, pointValues);
        processSearchUpdate(context, internalURI, pointKeys, pointValues);
    }

    @Override
    public void addStringsToSeries(CallingContext context, String seriesURI, List<String> pointKeys,
            List<String> pointValues) {
        checkParameter("URI", seriesURI);
        checkParameter("pointKeys", pointKeys);
        checkParameter("pointValues", pointValues);
        if (pointKeys.size() != pointValues.size()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("ArgSizeNotEqual")); //$NON-NLS-1$
        }
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addStringsToSeries(internalURI.getDocPath(), pointKeys, pointValues);
        processSearchUpdate(context, internalURI, pointKeys, pointValues);
    }

    @Override
    public void addStructuresToSeries(CallingContext context, String seriesURI, List<String> pointKeys,
            List<String> pointValues) {
        checkParameter("URI", seriesURI);
        checkParameter("pointKeys", pointKeys);
        checkParameter("pointValues", pointValues);

        if (pointKeys.size() != pointValues.size()) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("ArgSizeNotEqual")); //$NON-NLS-1$
        }
        for (String v : pointValues) {
            if (v == null)
                throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                        apiMessageCatalog.getMessage("NullEmpty", "pointValue")); //$NON-NLS-1$
        }
        RaptureURI internalURI = new RaptureURI(seriesURI, Scheme.SERIES);
        getRepoOrFail(internalURI).addStructuresToSeries(internalURI.getDocPath(), pointKeys, pointValues);
        processSearchUpdate(context, internalURI, pointKeys, pointValues);
    }

    @Override
    public List<SeriesPoint> runSeriesScript(CallingContext context, String scriptContent, List<String> arguments) {
        Hose h = assemble(scriptContent, arguments);
        List<SeriesPoint> result = Lists.newArrayList();
        while (true) {
            SeriesValue v = h.pullValue();
            if (v == null)
                break;
            result.add(sv2xsf.apply(v));
        }
        return result;
    }

    @Override
    public void runSeriesScriptQuiet(CallingContext context, String scriptContent, List<String> arguments) {
        Hose h = assemble(scriptContent, arguments);
        while (h.pullValue() != null)
            ;
    }

    private Hose assemble(String program, List<String> args) {
        List<HoseArg> decodedArgs = Lists.transform(args, xsf2sv);
        try {
            return HoseProgram.compile(program).make(decodedArgs);
        } catch (RecognitionException e) {
            throw RaptureExceptionFactory.create(HttpURLConnection.HTTP_BAD_REQUEST,
                    apiMessageCatalog.getMessage("BadHose"), e);
        }
    }

    @Override
    public List<SeriesDouble> getPointsAsDoubles(CallingContext context, String seriesURI) {
        return Lists.transform(getPointsAsSeriesValues(context, seriesURI), sv2sd);
    }

    @Override
    public List<SeriesDouble> getPointsAfterAsDoubles(CallingContext context, String seriesURI, String startColumn,
            int maxNumber) {
        return Lists.transform(getPointsAfterAsSeriesValues(context, seriesURI, startColumn, maxNumber), sv2sd);
    }

    @Override
    public List<SeriesDouble> getPointsInRangeAsDoubles(CallingContext context, String seriesURI,
            String startColumn, String endColumn, int maxNumber) {
        return Lists.transform(
                getPointsInRangeAsSeriesValues(context, seriesURI, startColumn, endColumn, maxNumber), sv2sd);
    }

    @Override
    public List<SeriesString> getPointsAsStrings(CallingContext context, String seriesURI) {
        return Lists.transform(getPointsAsSeriesValues(context, seriesURI), sv2ss);
    }

    @Override
    public List<SeriesString> getPointsAfterAsStrings(CallingContext context, String seriesURI, String startColumn,
            int maxNumber) {
        return Lists.transform(getPointsAfterAsSeriesValues(context, seriesURI, startColumn, maxNumber), sv2ss);
    }

    @Override
    public List<SeriesString> getPointsInRangeAsStrings(CallingContext context, String seriesURI,
            String startColumn, String endColumn, int maxNumber) {
        return Lists.transform(
                getPointsInRangeAsSeriesValues(context, seriesURI, startColumn, endColumn, maxNumber), sv2ss);
    }

    @Override
    public List<SeriesRepoConfig> getSeriesRepoConfigs(CallingContext context) {
        List<SeriesRepoConfig> result = SeriesRepoConfigStorage.readAll();
        if (result == null)
            result = Lists.newArrayList();
        return result;
    }

    @Override
    public Map<String, RaptureFolderInfo> listSeriesByUriPrefix(CallingContext context, String uriPrefix,
            int depth) {
        RaptureURI internalUri = new RaptureURI(uriPrefix, SERIES);
        String authority = internalUri.getAuthority();
        Map<String, RaptureFolderInfo> ret = new HashMap<String, RaptureFolderInfo>();

        // Schema level is special case.
        if (authority.isEmpty()) {
            --depth;
            try {
                List<SeriesRepoConfig> configs = getSeriesRepoConfigs(context);
                for (SeriesRepoConfig config : configs) {
                    authority = config.getAuthority();
                    // NULL or empty string should not exist.
                    if ((authority == null) || authority.isEmpty()) {
                        log.warn("Invalid authority (null or empty string) found for "
                                + JacksonUtil.jsonFromObject(config));
                        continue;
                    }
                    String uri = new RaptureURI(authority, SERIES).toString();
                    ret.put(uri, new RaptureFolderInfo(authority, true));
                    if (depth != 0) {
                        ret.putAll(listSeriesByUriPrefix(context, uri, depth));
                    }
                }

            } catch (RaptureException e) {
                // permission denied
                log.debug("No read permission for " + uriPrefix);
            }
            return ret;
        }

        SeriesRepo repo = getRepoFromCache(internalUri.getAuthority());
        Boolean getAll = false;
        if (repo == null) {
            return ret;
        }
        String parentDocPath = internalUri.getDocPath() == null ? "" : internalUri.getDocPath();
        int startDepth = StringUtils.countMatches(parentDocPath, "/");

        if (log.isDebugEnabled()) {
            log.debug("Loading all children from repo " + internalUri.getAuthority() + " with "
                    + internalUri.getDocPath());
        }
        if (depth <= 0)
            getAll = true;

        Stack<String> parentsStack = new Stack<String>();
        parentsStack.push(parentDocPath);

        while (!parentsStack.isEmpty()) {
            String currParentDocPath = parentsStack.pop();
            int currDepth = StringUtils.countMatches(currParentDocPath, "/") - startDepth;
            if (!getAll && currDepth >= depth)
                continue;
            boolean top = currParentDocPath.isEmpty();
            // Make sure that you have permission to read the folder.
            try {
                ListSeriesByUriPrefixPayload requestObj = new ListSeriesByUriPrefixPayload();
                requestObj.setContext(context);
                requestObj.setSeriesUri(currParentDocPath);
                ContextValidator.validateContext(context, EntitlementSet.Series_listSeriesByUriPrefix, requestObj);
            } catch (RaptureException e) {
                // permission denied
                log.debug("No read permission on folder " + currParentDocPath);
                continue;
            }

            List<RaptureFolderInfo> children = repo.listSeriesByUriPrefix(currParentDocPath);
            if ((children == null) || (children.isEmpty()) && (currDepth == 0) && (internalUri.hasDocPath())) {
                return ret;
            } else {
                for (RaptureFolderInfo child : children) {
                    String childDocPath = currParentDocPath + (top ? "" : "/") + child.getName();
                    if (child.getName().isEmpty())
                        continue;
                    String childUri = RaptureURI.builder(Scheme.SERIES, authority).docPath(childDocPath).asString()
                            + (child.isFolder() ? "/" : "");
                    ret.put(childUri, child);
                    if (child.isFolder()) {
                        parentsStack.push(childDocPath);
                    }
                }
            }
            if (top)
                startDepth--; // special case
        }
        return ret;
    }

    private SeriesRepo getRepoFromCache(String authority) {
        return Kernel.getRepoCacheManager().getSeriesRepo(authority);
    }

    private SeriesRepoConfig getConfigFromCache(String authority) {
        return Kernel.getRepoCacheManager().getSeriesRepoConfig(authority);
    }

    private void removeRepoFromCache(String authority) {
        Kernel.getRepoCacheManager().removeRepo(Scheme.SERIES.toString(), authority);
    }

    @Override
    public List<String> deleteSeriesByUriPrefix(CallingContext context, String uriPrefix) {

        Map<String, RaptureFolderInfo> map = listSeriesByUriPrefix(context, uriPrefix, Integer.MAX_VALUE);
        List<RaptureURI> folders = new ArrayList<>();
        Set<String> notEmpty = new HashSet<>();
        List<String> removed = new ArrayList<>();
        RaptureURI serUri = new RaptureURI(uriPrefix, SERIES);
        SeriesRepo repository = getRepoOrFail(serUri);

        DeleteSeriesPayload requestObj = new DeleteSeriesPayload();
        requestObj.setContext(context);

        folders.add(serUri);
        for (Map.Entry<String, RaptureFolderInfo> entry : map.entrySet()) {
            String uri = entry.getKey();
            RaptureURI ruri = new RaptureURI(uri);
            boolean isFolder = entry.getValue().isFolder();
            try {
                requestObj.setSeriesUri(uri);
                if (isFolder) {
                    ContextValidator.validateContext(context, EntitlementSet.Series_deleteSeriesByUriPrefix,
                            requestObj);
                    folders.add(0, ruri);
                } else {
                    ContextValidator.validateContext(context, EntitlementSet.Series_deleteSeries, requestObj);
                    deletePointsFromSeries(context, uri);
                    deleteSeries(context, uri);
                    removed.add(uri);
                }
            } catch (RaptureException e) {
                // permission denied
                log.debug("Unable to delete " + uri + " : " + e.getMessage());
            }
        }
        for (RaptureURI uri : folders) {
            // deleteFolder returns true if the folder was deleted.
            // It won't delete a folder that isn't empty.
            while (uri != null) {
                if (!repository.deleteFolder(uri))
                    break;
                // getParentURI returns null if the URI has no doc path
                uri = uri.getParentURI();
            }
        }
        return removed;

    }

    static final String DUMMY = "dUmMy__dUmMy";
    static final List<String> DUMMYLIST = new ArrayList<String>(1);

    static {
        DUMMYLIST.add(DUMMY);
    }

    /**
     * Wonder if this is faster than calling getContent for docs and blobs?
     */
    @Override
    public Boolean seriesExists(CallingContext context, String seriesURI) {
        try {
            RaptureURI uri = new RaptureURI(seriesURI, Scheme.SERIES);
            // no authority or no doc path
            if (uri.getAuthority().isEmpty() || uri.getDocPathDepth() < 2) {
                return false;
            }
            if (getLastPoint(context, seriesURI) != null) {
                return true;
            }
            RaptureURI parent = uri.getParentURI();
            Map<String, RaptureFolderInfo> siblings = listSeriesByUriPrefix(context, parent.toString(), 1);
            return (siblings.get(uri.toString()) != null);
        } catch (Exception e) {
            log.info(ExceptionToString.format(e));
            return false;
        }
    }

    @Override
    public void deleteSeries(CallingContext context, String seriesURI) {
        RaptureURI uri = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repository = getRepoOrFail(uri);
        repository.deleteSeries(uri.getDocPath());
        SearchPublisher.publishDeleteMessage(context, getConfigFromCache(uri.getAuthority()), uri);
    }

    @Override
    public void createSeries(CallingContext context, String seriesURI) {
        RaptureURI uri = new RaptureURI(seriesURI, Scheme.SERIES);
        SeriesRepo repository = getRepoOrFail(uri);
        repository.createSeries(uri.getDocPath());
        processSearchUpdate(context, uri, new ArrayList<>(), new ArrayList<>());
    }

    private void processSearchUpdate(CallingContext ctx, RaptureURI seriesUri, String key, Object value) {
        processSearchUpdate(ctx, seriesUri, Arrays.asList(key), Arrays.asList(value));
    }

    private void processSearchUpdate(CallingContext ctx, RaptureURI seriesUri, List<String> keys,
            List<? extends Object> values) {
        SearchPublisher.publishCreateMessage(ctx, getConfigFromCache(seriesUri.getAuthority()),
                new SeriesUpdateObject(seriesUri.getShortPath(), keys, values));
    }

}