org.onexus.website.api.pages.search.SearchPage.java Source code

Java tutorial

Introduction

Here is the source code for org.onexus.website.api.pages.search.SearchPage.java

Source

/**
 *  Copyright 2012 Universitat Pompeu Fabra.
 *
 *  Licensed 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.onexus.website.api.pages.search;

import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.IAjaxIndicatorAware;
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.markup.html.form.RadioChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.EmptyPanel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.Response;
import org.apache.wicket.util.string.Strings;
import org.onexus.collection.api.Collection;
import org.onexus.collection.api.ICollectionManager;
import org.onexus.collection.api.IEntity;
import org.onexus.collection.api.query.Contains;
import org.onexus.collection.api.query.OrderBy;
import org.onexus.collection.api.query.Query;
import org.onexus.collection.api.utils.EntityIterator;
import org.onexus.collection.api.utils.QueryUtils;
import org.onexus.resource.api.IResourceManager;
import org.onexus.resource.api.ORI;
import org.onexus.website.api.WebsiteApplication;
import org.onexus.website.api.pages.Page;
import org.onexus.website.api.pages.search.boxes.BoxesPanel;
import org.onexus.website.api.utils.panels.ondomready.OnDomReadyPanel;
import org.onexus.website.api.widgets.selection.FilterConfig;
import org.onexus.website.api.widgets.selection.FiltersWidgetConfig;
import org.onexus.website.api.widgets.selection.FiltersWidgetStatus;

import javax.inject.Inject;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class SearchPage extends Page<SearchPageConfig, SearchPageStatus> implements IAjaxIndicatorAware {

    @Inject
    private ICollectionManager collectionManager;

    @Inject
    private IResourceManager resourceManager;

    private transient FiltersWidgetStatus filtersStatus;

    private transient FilterConfig userFilter;

    private TextField<String> search;

    private WebMarkupContainer indicator;

    public SearchPage(String componentId, IModel<SearchPageStatus> statusModel) {
        this(componentId, statusModel, true, true);
    }

    public SearchPage(String componentId, IModel<SearchPageStatus> statusModel, boolean showTypes,
            boolean showLogo) {
        super(componentId, statusModel);

        IModel<SearchPageStatus> pageStatusModel = new PropertyModel<SearchPageStatus>(this, "status");

        indicator = new WebMarkupContainer("indicator");
        indicator.setOutputMarkupId(true);
        indicator.add(new Image("loading", OnDomReadyPanel.LOADING_IMAGE));
        add(indicator);

        WebMarkupContainer container = new WebMarkupContainer("container");
        container.add(new AttributeModifier("class", showLogo ? "show-logo" : "hide-logo"));

        Form form = new Form<SearchPageStatus>("form");
        form.add(new AjaxButton("submit") {

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                setFiltersStatus(null);
                userFilter = null;
                SearchPage.this.onSubmit(SearchPage.this.getStatus(),
                        SearchPage.this.getConfig().getWebsiteConfig().getORI().getParent(), userFilter);
                target.add(SearchPage.this.get("boxes"));
            }

            @Override
            protected void onError(AjaxRequestTarget target, Form<?> form) {

            }
        });
        form.setMultiPart(true);

        // By default use the first search type
        List<SearchType> types = getConfig().getTypes();
        if (getStatus().getType() == null && !types.isEmpty()) {
            getStatus().setType(types.get(0));
        }

        search = new TextField<String>("search", new PropertyModel<String>(pageStatusModel, "search"));
        search.setOutputMarkupId(true);

        if (!Strings.isEmpty(getStatus().getType().getPlaceholder())) {
            search.add(new AttributeModifier("placeholder", getStatus().getType().getPlaceholder()));
        }

        search.add(new AutoCompleteBehavior<IEntity>(new EntityRenderer(), new AutoCompleteSettings()) {
            @Override
            protected Iterator<IEntity> getChoices(String input) {
                return getAutocompleteChoices(input);
            }

            @Override
            protected String findIndicatorId() {
                return null;
            }
        });

        form.add(search);

        // Filters list modal
        final WebMarkupContainer widgetModal = new WebMarkupContainer("widgetModal");
        widgetModal.setOutputMarkupId(true);
        widgetModal.add(new Label("header", ""));
        widgetModal.add(new EmptyPanel("widget"));
        widgetModal.add(new AjaxLink<String>("close") {
            @Override
            public void onClick(AjaxRequestTarget target) {
                target.appendJavaScript("$('#" + widgetModal.getMarkupId() + "').modal('hide')");
            }
        });
        form.add(widgetModal);
        final AjaxLink<String> list = new AjaxLink<String>("list") {

            @Override
            public void onClick(AjaxRequestTarget target) {

                FiltersWidgetConfig filters = getStatus().getType().getFilters();

                if (filters != null) {
                    FiltersWidgetStatus status = filters.createEmptyStatus();
                    status.setConfig(filters);
                    setFiltersStatus(status);
                    widgetModal.addOrReplace(new Label("header", filters.getTitle()));
                    widgetModal.addOrReplace(new SearchFiltersWidget("widget",
                            new PropertyModel<FiltersWidgetStatus>(SearchPage.this, "filtersStatus")) {

                        @Override
                        protected void applyFilter(FilterConfig filterConfig, AjaxRequestTarget target) {
                            filterConfig.setDeletable(true);
                            search.setModelValue(new String[] { filterConfig.getName() });
                            target.add(search);
                            userFilter = filterConfig;
                            SearchPage.this.addOrReplace(internalBoxesPanel(filterConfig));
                            target.add(SearchPage.this.get("boxes"));
                            target.appendJavaScript("$('#" + widgetModal.getMarkupId() + "').modal('hide')");
                        }
                    });
                    target.add(widgetModal);
                    target.appendJavaScript("$('#" + widgetModal.getMarkupId() + "').modal('show')");
                }
            }
        };
        list.setOutputMarkupPlaceholderTag(true);
        form.add(list);

        FiltersWidgetConfig filters = getStatus().getType().getFilters();
        if (filters == null) {
            list.setVisible(false);
        } else {
            list.add(new AttributeModifier("rel", "tooltip"));
            list.add(new AttributeModifier("title", filters.getTitle()));
            list.setVisible(true);
        }
        setFiltersStatus(null);

        // Choose type
        RadioChoice<SearchType> typeSelect = new RadioChoice<SearchType>("type",
                new PropertyModel<SearchType>(pageStatusModel, "type"), types, new SearchTypeRenderer());
        typeSelect.add(new AjaxFormChoiceComponentUpdatingBehavior() {
            @Override
            protected void onUpdate(AjaxRequestTarget target) {

                getStatus().setSearch("");

                if (!Strings.isEmpty(getStatus().getType().getPlaceholder())) {
                    search.add(new AttributeModifier("placeholder", getStatus().getType().getPlaceholder()));
                } else {
                    search.add(new AttributeModifier("placeholder", ""));
                }

                SearchPage.this.addOrReplace(internalBoxesPanel());

                target.add(search);
                target.add(SearchPage.this.get("container").get("form").get("examplesContainer"));
                target.add(SearchPage.this.get("boxes"));

                FiltersWidgetConfig filters = getStatus().getType().getFilters();
                if (filters == null) {
                    list.setVisible(false);
                } else {
                    list.setVisible(true);
                    list.add(new AttributeModifier("title", filters.getTitle()));
                }
                setFiltersStatus(null);
                target.add(list);
            }
        });
        form.add(typeSelect);
        typeSelect.setVisible(showTypes);

        container.add(form);
        add(container);

        // Examples
        WebMarkupContainer examples = new WebMarkupContainer("examplesContainer");
        examples.setOutputMarkupId(true);
        examples.add(new ListView<SearchExample>("examples",
                new ExamplesModel(new PropertyModel<SearchType>(pageStatusModel, "type"))) {

            @Override
            protected void populateItem(ListItem<SearchExample> item) {

                AjaxLink<SearchExample> link = new AjaxLink<SearchExample>("link", item.getModel()) {
                    @Override
                    public void onClick(AjaxRequestTarget target) {
                        onSearch(target, getModelObject().getQuery());
                    }
                };

                link.add(new Label("label", new PropertyModel<String>(item.getModel(), "label")));
                item.add(link);

                WebMarkupContainer sep = new WebMarkupContainer("sep");
                sep.setVisible(item.getIndex() + 1 != getModelObject().size());
                item.add(sep);

            }
        });

        form.add(examples);

        add(internalBoxesPanel());

    }

    @Override
    public String getAjaxIndicatorMarkupId() {
        return indicator.getMarkupId();
    }

    public FiltersWidgetStatus getFiltersStatus() {
        return filtersStatus;
    }

    public void setFiltersStatus(FiltersWidgetStatus filtersStatus) {
        this.filtersStatus = filtersStatus;
    }

    private BoxesPanel internalBoxesPanel(FilterConfig filter) {
        return newBoxesPanel(getStatus(), getConfig().getWebsiteConfig().getORI().getParent(), filter);
    }

    private BoxesPanel internalBoxesPanel() {
        return newBoxesPanel(getStatus(), getConfig().getWebsiteConfig().getORI().getParent(), null);
    }

    private BoxesPanel newBoxesPanel(SearchPageStatus status, ORI baseUri, FilterConfig filter) {
        return new BoxesPanel("boxes", status, baseUri, filter) {
            @Override
            protected void onDisambiguation(AjaxRequestTarget target, String query) {
                onSearch(target, query);
            }
        };
    }

    protected void onSearch(AjaxRequestTarget target, String query) {
        getStatus().setSearch(query);
        addOrReplace(internalBoxesPanel());
        target.add(search);
        target.add(SearchPage.this.get("boxes"));
    }

    protected void onSubmit(SearchPageStatus status, ORI baseUri, FilterConfig filter) {
        addOrReplace(newBoxesPanel(status, baseUri, filter));
    }

    private Iterator<IEntity> getAutocompleteChoices(String in) {

        int lastComma = in.lastIndexOf(',');
        String input = lastComma > -1 ? in.substring(lastComma + 1).trim() : in.trim();

        Query query = new Query();
        SearchType type = getStatus().getType();

        ORI collectionUri = getAbsoluteUri(type.getCollection());
        String collectionAlias = QueryUtils.newCollectionAlias(query, collectionUri);
        query.setFrom(collectionAlias);

        List<String> fieldList = type.getFieldsList();
        query.addSelect(collectionAlias, fieldList);

        for (String field : fieldList) {
            QueryUtils.or(query, new Contains(collectionAlias, field, input));
        }
        query.addOrderBy(new OrderBy(collectionAlias, fieldList.get(0)));

        query.setCount(10);

        return new EntityIterator(collectionManager.load(query), collectionUri);
    }

    private ORI getAbsoluteUri(ORI partialUri) {
        ORI baseUri = SearchPage.this.getConfig().getWebsiteConfig().getORI().getParent();
        return partialUri.toAbsolute(baseUri);
    }

    private IResourceManager getResourceManager() {
        if (resourceManager == null) {
            WebsiteApplication.inject(this);
        }

        return resourceManager;
    }

    private class SearchTypeRenderer implements IChoiceRenderer<SearchType> {

        @Override
        public Object getDisplayValue(SearchType type) {

            ORI collectionUri = type.getCollection();

            Collection collection = getResourceManager().load(Collection.class, getAbsoluteUri(collectionUri));

            if (collection == null) {
                return collectionUri;
            }

            String title = collection.getTitle();
            return title == null ? collection.getName() : title;
        }

        @Override
        public String getIdValue(SearchType object, int index) {
            return Integer.toString(index);
        }
    }

    /**
     * Generic IEntity renderer that show all the fields.
     */
    private class EntityRenderer implements IAutoCompleteRenderer<IEntity> {

        public final void render(final IEntity object, final Response response, final String criteria) {
            String textValue = getTextValue(object, criteria);
            if (textValue == null) {
                throw new IllegalStateException(
                        "A call to textValue(Object) returned an illegal value: null for object: "
                                + object.toString());
            }
            textValue = textValue.replaceAll("\\\"", "&quot;");

            response.write("<li textvalue=\"" + textValue + "\"");
            response.write(">");
            renderChoice(object, response, criteria);
            response.write("</li>");
        }

        private String getTextValue(IEntity object, String in) {

            int lastComma = in.lastIndexOf(',');
            String criteria = lastComma > -1 ? in.substring(lastComma + 1).trim() : in.trim();
            String previous = lastComma > -1 ? in.substring(0, lastComma) + ", " : "";

            SearchType type = getStatus().getType();
            List<String> fields = type.getKeysList();

            for (String field : fields) {
                String value = String.valueOf(object.get(field));

                if (StringUtils.containsIgnoreCase(value, criteria)) {
                    return previous + value;
                }
            }

            return previous + object.get(fields.get(0));
        }

        public final void renderHeader(final Response response) {
            response.write("<ul>");
        }

        public final void renderFooter(final Response response, int count) {
            response.write("</ul>");
        }

        protected void renderChoice(IEntity object, Response response, String criteria) {

            SearchType type = getStatus().getType();
            List<String> keys = type.getKeysList();
            List<String> fields = type.getFieldsList();

            boolean keyFieldAdded = false;
            for (String field : fields) {
                String value = String.valueOf(object.get(field));

                if (StringUtils.containsIgnoreCase(value, criteria)) {
                    if (keys.contains(field)) {
                        keyFieldAdded = true;
                    }

                    renderValue(response, value, criteria, field);

                }
            }

            if (!keyFieldAdded) {
                String field = keys.get(0);
                String value = String.valueOf(object.get(field));
                renderValue(response, value, criteria, field);
            }
        }

        private void renderValue(Response response, String value, String criteria, String field) {

            String hlValue = value.replaceAll("(?i)(" + criteria + ")", "<strong>$1</strong>");
            response.write("<span class='f'>" + field.toLowerCase() + ":</span>" + hlValue);
            response.write("<br />");
        }

    }

    private final class ExamplesModel extends AbstractReadOnlyModel<List<SearchExample>> {

        private IModel<SearchType> model;

        private ExamplesModel(IModel<SearchType> model) {
            this.model = model;
        }

        @Override
        public List<SearchExample> getObject() {

            SearchType searchType = model.getObject();

            if (searchType == null || searchType.getExamples() == null) {
                return Collections.EMPTY_LIST;
            }

            List<SearchExample> values = new ArrayList<SearchExample>();
            for (String value : searchType.getExamples().split(",")) {

                String label = value.trim();
                String query = label;

                if (label.contains("|")) {
                    String[] split = label.split("\\|");
                    label = split[0].trim();
                    query = split[1].replace(';', ',');
                }

                values.add(new SearchExample(label, query));
            }
            return values;
        }
    }

    private static class SearchExample implements Serializable {

        private String label;
        private String query;

        public SearchExample() {
        }

        public SearchExample(String label, String query) {
            this.label = label;
            this.query = query;
        }

        public String getLabel() {
            return label;
        }

        public void setLabel(String label) {
            this.label = label;
        }

        public String getQuery() {
            return query;
        }

        public void setQuery(String query) {
            this.query = query;
        }
    }

}