org.waveprotocol.box.server.waveserver.QueryHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.waveprotocol.box.server.waveserver.QueryHelper.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.waveprotocol.box.server.waveserver;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

import org.waveprotocol.wave.model.id.IdUtil;
import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.WaveViewData;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Helper class that allows to add basic sort and filter functionality to the
 * search.
 *
 * @author yurize@apache.org (Yuri Zelikov)
 */
public class QueryHelper {

    @SuppressWarnings("serial")
    public static class InvalidQueryException extends Exception {

        public InvalidQueryException(String msg) {
            super(msg);
        }
    }

    /**
     * Unknown participantId used by {@link ASC_CREATOR_COMPARATOR} in case wave
     * creator cannot be found.
     */
    static final ParticipantId UNKNOWN_CREATOR = ParticipantId.ofUnsafe("unknown@example.com");

    /** Sorts search result in ascending order by LMT (Last Modified Time). */
    static final Comparator<WaveViewData> ASC_LMT_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            long lmt0 = computeLmt(arg0);
            long lmt1 = computeLmt(arg1);
            return Long.signum(lmt0 - lmt1);
        }

        private long computeLmt(WaveViewData wave) {
            long lmt = -1;
            for (ObservableWaveletData wavelet : wave.getWavelets()) {
                // Skip non conversational wavelets.
                if (!IdUtil.isConversationalId(wavelet.getWaveletId())) {
                    continue;
                }
                lmt = lmt < wavelet.getLastModifiedTime() ? wavelet.getLastModifiedTime() : lmt;
            }
            return lmt;
        }
    };

    /** Sorts search result in descending order by LMT (Last Modified Time). */
    public static final Comparator<WaveViewData> DESC_LMT_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            return -ASC_LMT_COMPARATOR.compare(arg0, arg1);
        }
    };

    /** Sorts search result in ascending order by creation time. */
    public static final Comparator<WaveViewData> ASC_CREATED_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            long time0 = computeCreatedTime(arg0);
            long time1 = computeCreatedTime(arg1);
            return Long.signum(time0 - time1);
        }

        private long computeCreatedTime(WaveViewData wave) {
            long creationTime = -1;
            for (ObservableWaveletData wavelet : wave.getWavelets()) {
                creationTime = creationTime < wavelet.getCreationTime() ? wavelet.getCreationTime() : creationTime;
            }
            return creationTime;
        }
    };

    /** Sorts search result in descending order by creation time. */
    public static final Comparator<WaveViewData> DESC_CREATED_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            return -ASC_CREATED_COMPARATOR.compare(arg0, arg1);
        }
    };

    /** Sorts search result in ascending order by creator */
    public static final Comparator<WaveViewData> ASC_CREATOR_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            ParticipantId creator0 = computeCreator(arg0);
            ParticipantId creator1 = computeCreator(arg1);
            return creator0.compareTo(creator1);
        }

        private ParticipantId computeCreator(WaveViewData wave) {
            for (ObservableWaveletData wavelet : wave.getWavelets()) {
                if (IdUtil.isConversationRootWaveletId(wavelet.getWaveletId())) {
                    return wavelet.getCreator();
                }
            }
            // If not found creator - compare with UNKNOWN_CREATOR;
            return UNKNOWN_CREATOR;
        }
    };

    /** Sorts search result in descending order by creator */
    public static final Comparator<WaveViewData> DESC_CREATOR_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            return -ASC_CREATOR_COMPARATOR.compare(arg0, arg1);
        }
    };

    /** Sorts search result by WaveId. */
    public static final Comparator<WaveViewData> ID_COMPARATOR = new Comparator<WaveViewData>() {
        @Override
        public int compare(WaveViewData arg0, WaveViewData arg1) {
            return arg0.getWaveId().compareTo(arg1.getWaveId());
        }
    };

    /**
     * Orders using {@link ASCENDING_DATE_COMPARATOR}.
     */
    public static final Ordering<WaveViewData> ASC_LMT_ORDERING = Ordering.from(QueryHelper.ASC_LMT_COMPARATOR);

    /**
     * Orders using {@link DESCENDING_DATE_COMPARATOR}.
     */
    public static final Ordering<WaveViewData> DESC_LMT_ORDERING = Ordering.from(QueryHelper.DESC_LMT_COMPARATOR);

    /**
     * Orders using {@link ASC_CREATED_COMPARATOR}.
     */
    public static final Ordering<WaveViewData> ASC_CREATED_ORDERING = Ordering
            .from(QueryHelper.ASC_CREATED_COMPARATOR);

    /**
     * Orders using {@link DESC_CREATED_COMPARATOR}.
     */
    public static final Ordering<WaveViewData> DESC_CREATED_ORDERING = Ordering
            .from(QueryHelper.DESC_CREATED_COMPARATOR);

    /**
     * Orders using {@link ASC_CREATOR_COMPARATOR}.
     */
    public static final Ordering<WaveViewData> ASC_CREATOR_ORDERING = Ordering
            .from(QueryHelper.ASC_CREATOR_COMPARATOR);

    /**
     * Orders using {@link DESC_CREATOR_COMPARATOR}.
     */
    public static final Ordering<WaveViewData> DESC_CREATOR_ORDERING = Ordering
            .from(QueryHelper.DESC_CREATOR_COMPARATOR);

    /** Default ordering is by LMT descending. */
    public static final Ordering<WaveViewData> DEFAULT_ORDERING = DESC_LMT_ORDERING;

    /** Registered order by parameter types and corresponding orderings. */
    public enum OrderByValueType {
        DATEASC("dateasc", ASC_LMT_ORDERING), DATEDESC("datedesc", DESC_LMT_ORDERING), CREATEDASC("createdasc",
                ASC_CREATED_ORDERING), CREATEDDESC("createddesc", DESC_CREATED_ORDERING), CREATORASC("creatorasc",
                        ASC_CREATOR_ORDERING), CREATORDESC("creatordesc", DESC_CREATOR_ORDERING);

        final String token;
        final Ordering<WaveViewData> ordering;

        OrderByValueType(String value, Ordering<WaveViewData> ordering) {
            this.token = value;
            this.ordering = ordering;
        }

        public String getToken() {
            return token;
        }

        public Ordering<WaveViewData> getOrdering() {
            return ordering;
        }

        private static final Map<String, OrderByValueType> reverseLookupMap = new HashMap<String, OrderByValueType>();

        static {
            for (OrderByValueType type : OrderByValueType.values()) {
                reverseLookupMap.put(type.getToken(), type);
            }
        }

        public static OrderByValueType fromToken(String token) {
            OrderByValueType orderByValue = reverseLookupMap.get(token);
            if (orderByValue == null) {
                throw new IllegalArgumentException("Illegal 'orderby' value: " + token);
            }
            return reverseLookupMap.get(token);
        }
    }

    /**
     * Builds a list of participants to serve as the filter for the query.
     *
     * @param queryParams the query params.
     * @param queryType the filter for the query , i.e. 'with'.
     * @param localDomain the local domain of the logged in user.
     * @return the participants list for the filter.
     * @throws InvalidParticipantAddress if participant id passed to the query is invalid.
     */
    public static List<ParticipantId> buildValidatedParticipantIds(Map<TokenQueryType, Set<String>> queryParams,
            TokenQueryType queryType, String localDomain) throws InvalidParticipantAddress {
        Set<String> tokenSet = queryParams.get(queryType);
        List<ParticipantId> participants = null;
        if (tokenSet != null) {
            participants = Lists.newArrayListWithCapacity(tokenSet.size());
            for (String token : tokenSet) {
                if (!token.isEmpty() && token.indexOf("@") == -1) {
                    // If no domain was specified, assume that the participant is from the local domain.
                    token = token + "@" + localDomain;
                } else if (token.equals("@")) {
                    // "@" is a shortcut for the shared domain participant.
                    token = "@" + localDomain;
                }
                ParticipantId otherUser = ParticipantId.of(token);
                participants.add(otherUser);
            }
        } else {
            participants = Collections.emptyList();
        }
        return participants;
    }

    /**
     * Computes ordering for the search results. If none are specified - then
     * returns the default ordering. The resulting ordering is always compounded
     * with ordering by wave id for stability.
     */
    public static Ordering<WaveViewData> computeSorter(Map<TokenQueryType, Set<String>> queryParams) {
        Ordering<WaveViewData> ordering = null;
        Set<String> orderBySet = queryParams.get(TokenQueryType.ORDERBY);
        if (orderBySet != null) {
            for (String orderBy : orderBySet) {
                QueryHelper.OrderByValueType orderingType = QueryHelper.OrderByValueType.fromToken(orderBy);
                if (ordering == null) {
                    // Primary ordering.
                    ordering = orderingType.getOrdering();
                } else {
                    // All other ordering are compounded to the primary one.
                    ordering = ordering.compound(orderingType.getOrdering());
                }
            }
        } else {
            ordering = QueryHelper.DEFAULT_ORDERING;
        }
        // For stability order also by wave id.
        ordering = ordering.compound(QueryHelper.ID_COMPARATOR);
        return ordering;
    }

    /**
     * Parses the search query.
     *
     * @param query the query.
     * @return the result map with query tokens. Never returns null.
     * @throws InvalidQueryException if the query contains invalid params.
     */
    public static Map<TokenQueryType, Set<String>> parseQuery(String query) throws InvalidQueryException {
        Preconditions.checkArgument(query != null);
        query = query.trim();
        // If query is empty - return.
        if (query.isEmpty()) {
            return Collections.emptyMap();
        }
        String[] tokens = query.split("\\s+");
        Map<TokenQueryType, Set<String>> tokensMap = Maps.newEnumMap(TokenQueryType.class);
        for (String token : tokens) {
            String[] pair = token.split(":");
            if (pair.length != 2 || !TokenQueryType.hasToken(pair[0])) {
                String msg = "Invalid query param: " + token;
                throw new InvalidQueryException(msg);
            }
            String tokenValue = pair[1];
            TokenQueryType tokenType = TokenQueryType.fromToken(pair[0]);
            // Verify the orderby param.
            if (tokenType.equals(TokenQueryType.ORDERBY)) {
                try {
                    OrderByValueType.fromToken(tokenValue);
                } catch (IllegalArgumentException e) {
                    String msg = "Invalid orderby query value: " + tokenValue;
                    throw new InvalidQueryException(msg);
                }
            }
            Set<String> valuesPerToken = tokensMap.get(tokenType);
            if (valuesPerToken == null) {
                valuesPerToken = Sets.newLinkedHashSet();
                tokensMap.put(tokenType, valuesPerToken);
            }
            valuesPerToken.add(tokenValue);
        }
        return tokensMap;
    }

    /** Private constructor to prevent instantiation. */
    private QueryHelper() {
    }
}