org.sakaiproject.nakamura.files.search.RelatedContentSearchPropertyProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.files.search.RelatedContentSearchPropertyProvider.java

Source

/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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.sakaiproject.nakamura.files.search;

import com.google.common.base.Joiner;

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.sakaiproject.nakamura.api.connections.ConnectionState;
import org.sakaiproject.nakamura.api.files.FilesConstants;
import org.sakaiproject.nakamura.api.lite.Session;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.authorizable.User;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.api.lite.content.ContentManager;
import org.sakaiproject.nakamura.api.search.solr.Query;
import org.sakaiproject.nakamura.api.search.solr.Result;
import org.sakaiproject.nakamura.api.search.solr.SolrSearchException;
import org.sakaiproject.nakamura.api.search.solr.SolrSearchPropertyProvider;
import org.sakaiproject.nakamura.api.search.solr.SolrSearchResultProcessor;
import org.sakaiproject.nakamura.api.search.solr.SolrSearchResultSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Create a feed that lists content related to the content in My Library. The criteria
 * that should be used for this are:
 * <p>
 * - Other content with similar words in the title</br> - Other content from my contact's
 * library</br> - Other content with similar tags</br> - Other content with similar
 * directory locations
 * </p>
 * 
 * When less than 11 items are found for these criteria, the feed should be filled up with
 * random content. However, preference should be given to items that have a thumbnail
 * (page1-small.jpg), a description, tags and comments.
 */
@Component(label = "RelatedContentSearchPropertyProvider", description = "Property provider for related content searches")
@Service
@Properties({ @Property(name = "service.vendor", value = "The Sakai Foundation"),
        @Property(name = "sakai.search.provider", value = "RelatedContentSearchPropertyProvider") })
public class RelatedContentSearchPropertyProvider extends MeManagerViewerSearchPropertyProvider
        implements SolrSearchPropertyProvider {

    private static final Logger LOG = LoggerFactory.getLogger(RelatedContentSearchPropertyProvider.class);

    private static final String DEFAULT_SEARCH_PROC_TARGET = "(&("
            + SolrSearchResultProcessor.DEFAULT_PROCESSOR_PROP + "=true))";
    @Reference(target = DEFAULT_SEARCH_PROC_TARGET)
    private transient SolrSearchResultProcessor defaultSearchProcessor;

    private static final int MAX_SOURCE_LIMIT = 100;

    /**
     * The solr query options that will be used in phase one where we find source content to
     * match against.
     */
    public static final Map<String, Object> SOURCE_QUERY_OPTIONS;
    static {
        final Map<String, Object> sqo = new HashMap<String, Object>(3);
        // sort by most recent content
        sqo.put("sort", "_lastModified desc");
        // limit source content for matching to something reasonable
        sqo.put("items", String.valueOf(MAX_SOURCE_LIMIT));
        sqo.put("page", "0");
        SOURCE_QUERY_OPTIONS = Collections.unmodifiableMap(sqo);
    }

    /**
     * Pattern to match any character that *might* be used to separate words. We only
     * compile the pattern *once* for performance.
     */
    private static final Pattern REGEX_PATTERN = Pattern
            .compile("(\\s|[\\Q`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?\\E])+");

    /**
     * A goofy string that we will use in edges cases where the search template requires we
     * supply a non-null, non-empty set of values. Very unlikely anything will ever match
     * this string.
     */
    private static final String AVOID_FALSE_POSITIVE_MATCHES = ClientUtils
            .escapeQueryChars(REGEX_PATTERN.pattern());

    /**
     * {@inheritDoc}
     * 
     * @see org.sakaiproject.nakamura.files.search.MeManagerViewerSearchPropertyProvider#loadUserProperties(org.apache.sling.api.SlingHttpServletRequest,
     *      java.util.Map)
     */
    public void loadUserProperties(final SlingHttpServletRequest request, final Map<String, String> propertiesMap) {

        /* phase one - find source content to match against */

        final String user = super.getUser(request);
        if (User.ANON_USER.equals(user)) {
            // stop here, anonymous is not a manager or a viewer of anything
            return;
        }

        final Session session = StorageClientUtils
                .adaptToSession(request.getResourceResolver().adaptTo(javax.jcr.Session.class));
        final Set<String> managers = super.getPrincipals(session, user, 1);
        final Set<String> viewers = new HashSet<String>(managers);

        final StringBuilder sourceQuery = new StringBuilder("resourceType:sakai/pooled-content AND (manager:(");
        sourceQuery.append(Joiner.on(" OR ").join(managers));
        sourceQuery.append(") OR viewer:(");
        sourceQuery.append(Joiner.on(" OR ").join(viewers));
        sourceQuery.append("))");
        final Query query = new Query(Query.SOLR, sourceQuery.toString(), SOURCE_QUERY_OPTIONS);

        SolrSearchResultSet rs = null;
        try {
            rs = defaultSearchProcessor.getSearchResultSet(request, query);
        } catch (SolrSearchException e) {
            LOG.error(e.getLocalizedMessage(), e);
            throw new IllegalStateException(e);
        }
        if (rs != null) {
            try {
                final ContentManager contentManager = session.getContentManager();
                final Iterator<Result> i = rs.getResultSetIterator();
                Set<String> allFileNames = new HashSet<String>();
                Set<String> allTags = new HashSet<String>();
                int count = 0;
                while (i.hasNext() && count < MAX_SOURCE_LIMIT) {
                    final Result result = i.next();
                    final String path = (String) result.getFirstValue("path");
                    final Content content = contentManager.get(path);
                    if (content != null) {
                        // grab the unique file name tokens
                        String fileName = (String) content.getProperty(FilesConstants.POOLED_CONTENT_FILENAME);
                        if (fileName != null) {
                            final String fileExtension = (String) content.getProperty("sakai:fileextension");
                            if (fileExtension != null) {
                                final int extensionIndex = fileName.lastIndexOf(fileExtension);
                                if (extensionIndex > 0) {
                                    fileName = fileName.substring(0, extensionIndex);
                                }
                            }
                            final String[] foundFileNames = REGEX_PATTERN.split(fileName);
                            for (String foundFileName : foundFileNames) {
                                if (!StringUtils.isBlank(foundFileName)) {
                                    allFileNames.add(foundFileName);
                                }
                            }
                        }

                        // grab all the unique tags
                        final String[] tags = PropertiesUtil
                                .toStringArray(content.getProperty(FilesConstants.SAKAI_TAGS));
                        if (tags != null) {
                            allTags.addAll(Arrays.asList(tags));
                        }
                    } else {
                        // fail quietly in this edge case
                        LOG.debug("Content not found: {}", path);
                    }
                    count++;
                }

                /* phase two - provide properties for final search */

                final List<String> connections = connectionManager.getConnectedUsers(request, user,
                        ConnectionState.ACCEPTED);
                if (connections != null) {
                    for (final String connection : connections) {
                        managers.add(ClientUtils.escapeQueryChars(connection));
                    }
                }
                managers.remove(user); // do not display my own content
                viewers.remove(user); // do not display my own content
                if (managers.isEmpty()) { // to prevent solr parse errors
                    managers.add(AVOID_FALSE_POSITIVE_MATCHES);
                }
                propertiesMap.put("managers", Joiner.on(" OR ").join(managers));
                if (viewers.isEmpty()) { // to prevent solr parse errors
                    viewers.add(AVOID_FALSE_POSITIVE_MATCHES);
                }
                propertiesMap.put("viewers", Joiner.on(" OR ").join(viewers));

                if (allFileNames.isEmpty()) { // to prevent solr parse errors
                    allFileNames.add(AVOID_FALSE_POSITIVE_MATCHES);
                }
                if (allFileNames.size() > 1024) {
                    /*
                     * solr allows a maximum of 1024. Performance will likely be an issue by this
                     * point.
                     */
                    LOG.warn("Exceeded maximum number of solr binary operations: {}. Reduced size to 1024.",
                            allFileNames.size());
                    final String[] tooLarge = (String[]) allFileNames.toArray();
                    final String[] justRight = Arrays.copyOf(tooLarge, 1024);
                    allFileNames = new HashSet<String>(Arrays.asList(justRight));
                }
                propertiesMap.put("fileNames", Joiner.on(" OR ").join(allFileNames));

                if (allTags.isEmpty()) { // to prevent solr parse errors
                    allTags.add(AVOID_FALSE_POSITIVE_MATCHES);
                }
                if (allTags.size() > 1024) {
                    /*
                     * solr allows a maximum of 1024. Performance will likely be an issue by this
                     * point.
                     */
                    LOG.warn("Exceeded maximum number of solr binary operations: {}. Reduced size to 1024.",
                            allTags.size());
                    final String[] tooLarge = (String[]) allTags.toArray();
                    final String[] justRight = Arrays.copyOf(tooLarge, 1024);
                    allTags = new HashSet<String>(Arrays.asList(justRight));
                }
                propertiesMap.put("tags", Joiner.on(" OR ").join(allTags));

            } catch (AccessDeniedException e) {
                LOG.error(e.getLocalizedMessage(), e);
            } catch (StorageClientException e) {
                LOG.error(e.getLocalizedMessage(), e);
            }
        }
    }

}