/* * 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.apache.syncope.client.console.wicket.markup.html.form; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; import org.apache.wicket.extensions.markup.html.form.palette.Palette; import org.apache.wicket.extensions.markup.html.form.palette.component.Recorder; 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.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.util.string.Strings; public class AjaxPalettePanel<T extends Serializable> extends AbstractFieldPanel<List<T>> { private static final long serialVersionUID = 7738499668258805567L; protected Palette<T> palette; private final Model<String> queryFilter = new Model<>(StringUtils.EMPTY); private final List<T> availableBefore = new ArrayList<>(); private final LoadableDetachableModel<List<T>> choicesModel; public AjaxPalettePanel( final String id, final IModel<List<T>> model, final Builder.Query<T> choices, final Builder<T> builder) { super(id, builder.name == null ? id : builder.name, model); choicesModel = new PaletteLoadableDetachableModel(builder) { private static final long serialVersionUID = -108100712154481840L; @Override protected List<T> getChoices() { return choices.execute(getFilter()); } }; initialize(model, builder); } public AjaxPalettePanel( final String id, final IModel<List<T>> model, final IModel<List<T>> choices, final Builder<T> builder) { super(id, builder.name == null ? id : builder.name, model); choicesModel = new PaletteLoadableDetachableModel(builder) { private static final long serialVersionUID = -108100712154481840L; @Override protected List<T> getChoices() { return builder.filtered ? getFilteredList(choices.getObject(), getFilter().replaceAll("\\*", "\\.\\*")) : choices.getObject(); } }; initialize(model, builder); } private void initialize(final IModel<List<T>> model, final Builder<T> builder) { setOutputMarkupId(true); this.palette = new NonI18nPalette<T>( "paletteField", model, choicesModel, builder.renderer, 8, builder.allowOrder, builder.allowMoveAll) { private static final long serialVersionUID = -3074655279011678437L; @Override protected Component newAvailableHeader(final String componentId) { return new Label(componentId, new ResourceModel("palette.available", builder.availableLabel)); } @Override protected Component newSelectedHeader(final String componentId) { return new Label(componentId, new ResourceModel("palette.selected", builder.selectedLabel)); } @Override protected Recorder<T> newRecorderComponent() { return new Recorder<T>("recorder", this) { private static final long serialVersionUID = -9169109967480083523L; @Override public List<T> getUnselectedList() { final IChoiceRenderer<? super T> renderer = getPalette().getChoiceRenderer(); final Collection<? extends T> choices = getPalette().getChoices(); final List<T> unselected = new ArrayList<>(choices.size()); final List<String> ids = Arrays.asList(getValue().split(",")); for (final T choice : choices) { final String choiceId = renderer.getIdValue(choice, 0); if (!ids.contains(choiceId)) { unselected.add(choice); } } return unselected; } @Override public List<T> getSelectedList() { final IChoiceRenderer<? super T> renderer = getPalette().getChoiceRenderer(); final Collection<? extends T> choices = getPalette().getChoices(); final List<T> selected = new ArrayList<>(choices.size()); // reduce number of method calls by building a lookup table final Map<T, String> idForChoice = new HashMap<>(choices.size()); for (final T choice : choices) { idForChoice.put(choice, renderer.getIdValue(choice, 0)); } final String value = getValue(); int start = value.indexOf(';') + 1; for (final String id : Strings.split(value.substring(start), ',')) { for (final T choice : choices) { final String idValue = idForChoice.get(choice); if (id.equals(idValue)) { selected.add(choice); break; } } } return selected; } }; } }; add(palette.setOutputMarkupId(true)); final Form<?> form = new Form<>("form"); add(form.setEnabled(builder.filtered).setVisible(builder.filtered)); final AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", queryFilter, false); filter.hideLabel().setOutputMarkupId(true); form.add(filter); form.add(new AjaxSubmitLink("search") { private static final long serialVersionUID = -1765773642975892072L; @Override protected void onAfterSubmit(final AjaxRequestTarget target, final Form<?> form) { super.onAfterSubmit(target, form); target.add(palette); } }); } public LoadableDetachableModel<List<T>> getChoicesModel() { return choicesModel; } @Override public AjaxPalettePanel<T> setModelObject(final List<T> object) { palette.setDefaultModelObject(object); return this; } public Collection<T> getModelCollection() { return palette.getModelCollection(); } public void reload(final AjaxRequestTarget target) { target.add(palette); } public static class Builder<T extends Serializable> implements Serializable { private static final long serialVersionUID = 991248996001040352L; private IChoiceRenderer<T> renderer; private boolean allowOrder; private boolean allowMoveAll; private String selectedLabel; private String availableLabel; private boolean filtered; private final AjaxPaletteConf conf = new AjaxPaletteConf(); private String filter = conf.getDefaultFilter(); private String name; public Builder() { this.allowMoveAll = false; this.allowOrder = false; this.filtered = false; this.renderer = new SelectChoiceRenderer<>(); } public Builder<T> setName(final String name) { this.name = name; return this; } public Builder<T> setAllowOrder(final boolean allowOrder) { this.allowOrder = allowOrder; return this; } public Builder<T> setAllowMoveAll(final boolean allowMoveAll) { this.allowMoveAll = allowMoveAll; return this; } public Builder<T> setSelectedLabel(final String selectedLabel) { this.selectedLabel = selectedLabel; return this; } public Builder<T> setAvailableLabel(final String availableLabel) { this.availableLabel = availableLabel; return this; } public Builder<T> setRenderer(final IChoiceRenderer<T> renderer) { this.renderer = renderer; return this; } public Builder<T> withFilter() { this.filtered = true; return this; } public Builder<T> withFilter(final String defaultFilter) { this.filtered = true; this.filter = defaultFilter; return this; } public AjaxPalettePanel<T> build(final String id, final IModel<List<T>> model, final IModel<List<T>> choices) { return new AjaxPalettePanel<>(id, model, choices, this); } public AjaxPalettePanel<T> build(final String id, final IModel<List<T>> model, final Query<T> choices) { return new AjaxPalettePanel<>(id, model, choices, this); } public abstract static class Query<T extends Serializable> implements Serializable { private static final long serialVersionUID = 3582312993557742858L; public abstract List<T> execute(final String filter); } } private abstract class PaletteLoadableDetachableModel extends LoadableDetachableModel<List<T>> { private static final long serialVersionUID = -7745220313769774616L; private final Builder<T> builder; PaletteLoadableDetachableModel(final Builder<T> builder) { super(); this.builder = builder; } protected abstract List<T> getChoices(); protected String getFilter() { return StringUtils.isBlank(queryFilter.getObject()) ? builder.filter : queryFilter.getObject(); } @Override protected List<T> load() { final List<T> selected = availableBefore.isEmpty() ? new ArrayList<>(palette.getModelCollection()) : getSelectedList(availableBefore, palette.getRecorderComponent().getValue()); availableBefore.clear(); availableBefore.addAll(ListUtils.sum(selected, getChoices())); return availableBefore; } private List<T> getSelectedList(final Collection<T> choices, final String selection) { final IChoiceRenderer<? super T> renderer = palette.getChoiceRenderer(); final List<T> selected = new ArrayList<>(); final Map<T, String> idForChoice = new HashMap<>(); for (final T choice : choices) { idForChoice.put(choice, renderer.getIdValue(choice, 0)); } for (final String id : Strings.split(selection, ',')) { final Iterator<T> iter = choices.iterator(); boolean found = false; while (!found && iter.hasNext()) { final T choice = iter.next(); final String idValue = idForChoice.get(choice); if (id.equals(idValue)) { selected.add(choice); found = true; } } } return selected; } protected List<T> getFilteredList(final Collection<T> choices, final String filter) { final IChoiceRenderer<? super T> renderer = palette.getChoiceRenderer(); final List<T> selected = new ArrayList<>(choices.size()); final Map<T, String> idForChoice = new HashMap<>(); for (final T choice : choices) { idForChoice.put(choice, renderer.getIdValue(choice, 0)); } final Pattern pattern = Pattern.compile(filter, Pattern.CASE_INSENSITIVE); for (T choice : choices) { final String idValue = idForChoice.get(choice); if (pattern.matcher(idValue).matches()) { selected.add(choice); } } return selected; } } }