com.xpn.xwiki.internal.objects.classes.UsedValuesListQueryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.internal.objects.classes.UsedValuesListQueryBuilder.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.internal.objects.classes;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.model.EntityType;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.query.Query;
import org.xwiki.query.QueryBuilder;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryFilter;
import org.xwiki.query.QueryManager;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.text.StringUtils;

import com.xpn.xwiki.objects.classes.ListClass;

/**
 * Builds a query that returns the values used by a List property.
 * 
 * @version $Id: deb6d2f22c007d5734d9844c02430c958a465327 $
 * @since 9.8RC1
 */
@Component
@Named("usedValues")
@Singleton
public class UsedValuesListQueryBuilder implements QueryBuilder<ListClass> {
    private class ViewableValueFilter implements QueryFilter {
        private final ListClass listClass;

        ViewableValueFilter(ListClass listClass) {
            this.listClass = listClass;
        }

        @Override
        public String filterStatement(String statement, String language) {
            // We only filter the results.
            return statement;
        }

        @Override
        public List filterResults(List results) {
            List<Object> filteredResults = new LinkedList<>();
            for (Object result : results) {
                Object[] row = (Object[]) result;
                String value = (String) row[0];
                Long count = (Long) row[1];
                if (UsedValuesListQueryBuilder.this.canView(this.listClass, value, count)) {
                    filteredResults.add(result);
                }
            }
            return filteredResults;
        }
    }

    @Inject
    private Logger logger;

    @Inject
    private QueryManager queryManager;

    @Inject
    private ContextualAuthorizationManager authorization;

    @Inject
    @Named("current")
    private DocumentReferenceResolver<String> documentReferenceResolver;

    @Override
    public Query build(ListClass listClass) throws QueryException {
        String statement = String.format("select %1$s, count(*) as unfilterable0 " + "from BaseObject as obj, %2$s "
                + "where obj.className = :className and obj.name <> :templateName"
                + " and prop.id.id = obj.id and prop.id.name = :propertyName " + "group by %1$s "
                + "order by count(*) desc", getSelectColumnAndFromTable(listClass));
        Query query = this.queryManager.createQuery(statement, Query.HQL);
        bindParameterValues(query, listClass);
        query.addFilter(new ViewableValueFilter(listClass));
        query.setWiki(listClass.getReference().extractReference(EntityType.WIKI).getName());
        return query;
    }

    private Object[] getSelectColumnAndFromTable(ListClass listClass) {
        String selectColumn = "prop.textValue";
        String fromTable = "StringListProperty as prop";
        if (!listClass.isMultiSelect()) {
            selectColumn = "prop.value";
            fromTable = "StringProperty as prop";
        } else if (listClass.isRelationalStorage()) {
            selectColumn = "listItem";
            fromTable = "DBStringListProperty as prop join prop.list listItem";
        }
        return new Object[] { selectColumn, fromTable };
    }

    private void bindParameterValues(Query query, ListClass listClass) {
        String className = listClass.getClassName();
        query.bindValue("className", className);
        query.bindValue("propertyName", listClass.getName());
        query.bindValue("templateName", getTemplateName(className));
    }

    private String getTemplateName(String className) {
        return StringUtils.removeEnd(className, "Class") + "Template";
    }

    private boolean canView(ListClass listClass, String value, long count) {
        // We can't check all the documents where this value occurs so we check just one of them, chosen randomly.
        long offset = ThreadLocalRandom.current().nextLong(count);
        String statement = String.format(
                "select obj.name from BaseObject as obj, %2$s "
                        + "where obj.className = :className and obj.name <> :templateName "
                        + "and prop.id.id = obj.id and prop.id.name = :propertyName and %1$s = :propertyValue",
                getSelectColumnAndFromTable(listClass));
        try {
            Query query = this.queryManager.createQuery(statement, Query.HQL);
            bindParameterValues(query, listClass);
            query.bindValue("propertyValue", value);
            query.setWiki(listClass.getReference().extractReference(EntityType.WIKI).getName());
            query.setOffset((int) offset).setLimit(1);
            List<?> results = query.execute();
            if (results.size() > 0) {
                DocumentReference documentReference = this.documentReferenceResolver
                        .resolve((String) results.get(0));
                if (this.authorization.hasAccess(Right.VIEW, documentReference)) {
                    return true;
                }
            }
        } catch (QueryException e) {
            this.logger.warn(
                    "Failed to check if the list value is viewable. Root cause is [{}]."
                            + " Continue assuming the value is not viewable.",
                    ExceptionUtils.getRootCauseMessage(e));
        }
        return false;
    }
}