| /** |
| * 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.Function; |
| import com.google.common.collect.LinkedHashMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| |
| import org.waveprotocol.box.server.util.WaveletDataUtil; |
| import org.waveprotocol.wave.model.id.IdConstants; |
| import org.waveprotocol.wave.model.id.IdUtil; |
| import org.waveprotocol.wave.model.id.WaveId; |
| import org.waveprotocol.wave.model.id.WaveletId; |
| import org.waveprotocol.wave.model.id.WaveletName; |
| import org.waveprotocol.wave.model.wave.ParticipantId; |
| import org.waveprotocol.wave.model.wave.ParticipantIdUtil; |
| import org.waveprotocol.wave.model.wave.data.ObservableWaveletData; |
| import org.waveprotocol.wave.model.wave.data.ReadableWaveletData; |
| import org.waveprotocol.wave.model.wave.data.WaveViewData; |
| import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl; |
| import org.waveprotocol.wave.util.logging.Log; |
| |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Base implementation of search provider. |
| * |
| * @author yurize@apache.org (Yuri Zelikov) |
| */ |
| public abstract class AbstractSearchProviderImpl implements SearchProvider { |
| private static final Log LOG = Log.get(AbstractSearchProviderImpl.class); |
| |
| protected final WaveDigester digester; |
| protected final ParticipantId sharedDomainParticipantId; |
| private final WaveMap waveMap; |
| |
| public AbstractSearchProviderImpl(final String waveDomain, WaveDigester digester, WaveMap waveMap) { |
| this.digester = digester; |
| sharedDomainParticipantId = ParticipantIdUtil.makeUnsafeSharedDomainParticipantId(waveDomain); |
| this.waveMap = waveMap; |
| } |
| |
| protected List<WaveViewData> computeSearchResult(final ParticipantId user, int startAt, |
| int numResults, List<WaveViewData> results) { |
| int searchResultSize = results.size(); |
| // Check if we have enough results to return. |
| if (searchResultSize < startAt) { |
| return Collections.emptyList(); |
| } else { |
| int endAt = Math.min(startAt + numResults, searchResultSize); |
| return Lists.newArrayList(results.subList(startAt, endAt)); |
| } |
| } |
| |
| // TODO (yurize) : Refactor this method. It does two things: filtering and |
| // building waves. |
| protected LinkedHashMap<WaveId, WaveViewData> filterWavesViewBySearchCriteria( |
| Function<ReadableWaveletData, Boolean> matchesFunction, |
| LinkedHashMultimap<WaveId, WaveletId> currentUserWavesView) { |
| // Must use a map with stable ordering, since indices are meaningful. |
| LinkedHashMap<WaveId, WaveViewData> results = Maps.newLinkedHashMap(); |
| |
| // Loop over the user waves view. |
| for (WaveId waveId : currentUserWavesView.keySet()) { |
| Set<WaveletId> waveletIds = currentUserWavesView.get(waveId); |
| WaveViewData view = buildWaveViewData(waveId, waveletIds, matchesFunction, waveMap); |
| Iterable<? extends ObservableWaveletData> wavelets = view.getWavelets(); |
| boolean hasConversation = false; |
| for (ObservableWaveletData waveletData : wavelets) { |
| if (IdUtil.isConversationalId(waveletData.getWaveletId())) { |
| hasConversation = true; |
| break; |
| } |
| } |
| if ((view != null) && hasConversation) { |
| results.put(waveId, view); |
| } |
| } |
| return results; |
| } |
| |
| public static WaveViewData buildWaveViewData(WaveId waveId, Set<WaveletId> waveletIds, |
| Function<ReadableWaveletData, Boolean> matchesFunction, WaveMap waveMap) { |
| |
| WaveViewData view = WaveViewDataImpl.create(waveId); // Copy of the wave built up for search hits. |
| for (WaveletId waveletId : waveletIds) { |
| WaveletContainer waveletContainer = null; |
| WaveletName waveletname = WaveletName.of(waveId, waveletId); |
| |
| |
| // TODO (Yuri Z.) This loop collects all the wavelets that match the |
| // query, so the view is determined by the query. Instead we should |
| // look at the user's wave view and determine if the view matches the |
| // query. |
| try { |
| waveletContainer = waveMap.getWavelet(waveletname); |
| if ((waveletContainer == null) || !waveletContainer.applyFunction(matchesFunction)) { |
| continue; |
| } |
| // Just keep adding all the relevant wavelets in this wave. |
| view.addWavelet(waveletContainer.copyWaveletData()); |
| } catch (WaveletStateException e) { |
| LOG.warning("Failed to access wavelet " + waveletContainer.getWaveletName(), e); |
| } |
| } |
| return view; |
| } |
| |
| /** |
| * Verifies whether the wavelet matches the filter criteria. |
| * |
| * @param wavelet the wavelet. |
| * @param user the logged in user. |
| * @param sharedDomainParticipantId the shared domain participant id. |
| * @param isAllQuery true if the search results should include shared for this |
| * domain waves. |
| */ |
| protected boolean isWaveletMatchesCriteria(ReadableWaveletData wavelet, ParticipantId user, |
| ParticipantId sharedDomainParticipantId, boolean isAllQuery) |
| throws WaveletStateException { |
| // If it is user data wavelet for the user - return true. |
| if (IdUtil.isUserDataWavelet(wavelet.getWaveletId()) && wavelet.getCreator().equals(user)) { |
| return true; |
| } |
| // The wavelet should have logged in user as participant for 'in:inbox' |
| // query. |
| if (!isAllQuery && !wavelet.getParticipants().contains(user)) { |
| return false; |
| } |
| // Or if it is an 'all' query - then either logged in user or shared domain |
| // participant should be present in the wave. |
| if (isAllQuery |
| && !WaveletDataUtil.checkAccessPermission(wavelet, user, sharedDomainParticipantId)) { |
| return false; |
| } |
| // If not returned 'false' above - then logged in user is either |
| // explicit or implicit participant and therefore has access permission. |
| return true; |
| } |
| |
| /** |
| * Ensures that each wave in the current waves view has the user data wavelet by always adding |
| * it to the view. |
| */ |
| protected void ensureWavesHaveUserDataWavelet( |
| LinkedHashMultimap<WaveId, WaveletId> currentUserWavesView, ParticipantId user) { |
| WaveletId udw = |
| WaveletId.of(user.getDomain(), |
| IdUtil.join(IdConstants.USER_DATA_WAVELET_PREFIX, user.getAddress())); |
| Set<WaveId> waveIds = currentUserWavesView.keySet(); |
| for (WaveId waveId : waveIds) { |
| Set<WaveletId> waveletIds = currentUserWavesView.get(waveId); |
| waveletIds.add(udw); |
| } |
| } |
| } |