/* * 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.panels; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormChoiceComponentUpdatingBehavior; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.client.console.wizards.AjaxWizard; import org.apache.syncope.client.console.wizards.WizardMgtPanel; import org.apache.wicket.Component; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.core.util.lang.PropertyResolver; import org.apache.wicket.event.IEvent; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Check; import org.apache.wicket.markup.html.form.CheckGroup; import org.apache.wicket.markup.html.form.CheckGroupSelector; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.request.cycle.RequestCycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class ListViewPanel<T extends Serializable> extends WizardMgtPanel<T> { private static final long serialVersionUID = -7982691107029848579L; private static final Logger LOG = LoggerFactory.getLogger(ListViewPanel.class); public enum CheckAvailability { /** * No checks. */ NONE, /** * Enabled checks including check group selector. */ AVAILABLE, /** * Disabled checks. */ DISABLED } private final CheckGroupSelector groupSelector; private final Model<CheckAvailability> check; private final ListView<T> beans; private final List<T> listOfItems; /** * Table view of a list of beans. * * @param id id. * @param list list of item. * @param reference list item reference class. * @param includes Used to sort and restrict the set of bean's fields to be shown. * @param actions item actions. */ private ListViewPanel( final String id, final List<T> list, final Class<T> reference, final List<String> includes, final ActionsPanel<T> actions, final CheckAvailability check, final boolean reuseItem, final boolean wizardInModal, final IModel<? extends Collection<T>> model) { super(id, wizardInModal); setOutputMarkupId(true); this.check = Model.of(check); addInnerObject(new Label("caption", new ResourceModel("listview.caption", StringUtils.EMPTY))); final CheckGroup<T> checkGroup = new CheckGroup<>("group", model); checkGroup.setOutputMarkupId(true); checkGroup.add(new IndicatorAjaxFormChoiceComponentUpdatingBehavior() { private static final long serialVersionUID = -151291731388673682L; @Override protected void onUpdate(final AjaxRequestTarget target) { // ignore } }); addInnerObject(checkGroup); groupSelector = new CheckGroupSelector("groupselector", checkGroup); addInnerObject(groupSelector.setOutputMarkupId(true) .setOutputMarkupPlaceholderTag(true) .setVisible(this.check.getObject() == CheckAvailability.AVAILABLE) .setEnabled(this.check.getObject() == CheckAvailability.AVAILABLE)); final List<String> toBeIncluded; if (includes == null || includes.isEmpty()) { toBeIncluded = new ArrayList<>(); for (Field field : Arrays.asList(reference.getDeclaredFields())) { toBeIncluded.add(field.getName()); } } else { toBeIncluded = includes; } if (toBeIncluded.isEmpty()) { LOG.warn("No field has been retrieved from {}", reference.getName()); listOfItems = new ArrayList<>(); } else if (list == null || list.isEmpty()) { LOG.info("No item to be shown"); listOfItems = new ArrayList<>(); } else { listOfItems = list; if (LOG.isDebugEnabled()) { for (String field : toBeIncluded) { LOG.debug("Show field {}", field); } } } addInnerObject(header(toBeIncluded)); beans = new ListView<T>("beans", listOfItems) { private static final long serialVersionUID = 1L; @Override protected void populateItem(final ListItem<T> beanItem) { beanItem.add(new Check<>("check", beanItem.getModel(), checkGroup).setOutputMarkupId(true) .setOutputMarkupPlaceholderTag(true) .setVisible(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE || ListViewPanel.this.check.getObject() == CheckAvailability.DISABLED) .setEnabled(ListViewPanel.this.check.getObject() == CheckAvailability.AVAILABLE)); final T bean = beanItem.getModelObject(); final ListView<String> fields = new ListView<String>("fields", toBeIncluded) { private static final long serialVersionUID = 1L; @Override protected void populateItem(final ListItem<String> fieldItem) { fieldItem.add(getValueComponent(fieldItem.getModelObject(), bean)); } }; beanItem.add(fields); beanItem.add(actions.clone("actions", new Model<>(bean))); } }; beans.setOutputMarkupId(true); beans.setReuseItems(reuseItem); beans.setRenderBodyOnly(true); checkGroup.add(beans); } private ListView<String> header(final List<String> labels) { return new ListView<String>("names", labels) { private static final long serialVersionUID = 1L; @Override protected void populateItem(final ListItem<String> item) { item.add(new Label("name", new ResourceModel(item.getModelObject(), item.getModelObject()))); } }; } public void setCheckAvailability(final CheckAvailability check) { // used to perform selectable enabling check condition this.check.setObject(check); final AjaxRequestTarget target = RequestCycle.get().find(AjaxRequestTarget.class); // reload group selector target.add(groupSelector.setVisible(check == CheckAvailability.AVAILABLE)); // reload the list view panel target.add(ListViewPanel.this); } protected abstract Component getValueComponent(final String key, final T bean); /** * ListViewPanel builder. * * @param <T> list item reference type. */ public static class Builder<T extends Serializable> extends WizardMgtPanel.Builder<T> { private static final long serialVersionUID = -3643771352897992172L; private IModel<? extends Collection<T>> model = Model.of(Collections.<T>emptyList()); private final List<String> includes = new ArrayList<>(); private final ActionsPanel<T> actions; private List<T> items; private CheckAvailability check = CheckAvailability.NONE; private boolean reuseItem = true; private final Class<T> reference; public Builder(final Class<T> reference, final PageReference pageRef) { super(pageRef); this.reference = reference; this.items = null; this.actions = new ActionsPanel<T>("actions", null); } public Builder<T> setModel(final IModel<? extends Collection<T>> model) { this.model = model; return this; } /** * Sets list of items. * * @param items list of items. * @return current builder object. */ public Builder<T> setItems(final List<T> items) { this.items = items; return this; } /** * Adds item. * * @param item item. * @return current builder object. */ public Builder<T> addItem(final T item) { if (item == null) { return this; } if (this.items == null) { this.items = new ArrayList<>(); } this.items.add(item); return this; } public Builder<T> withChecks(final CheckAvailability check) { this.check = check; return this; } public void setReuseItem(final boolean reuseItem) { this.reuseItem = reuseItem; } /** * Gives fields to be shown. It could be used to give an order as well. * * @param includes field names to be shown. * @return current builder object. */ public Builder<T> includes(final String... includes) { for (String include : includes) { if (include != null && !this.includes.contains(include)) { this.includes.add(include); } } return this; } /** * Add item action (the given order is ignored. * * @param link action link. * @param type action type. * @param entitlements entitlements. * @return current builder object. */ public Builder<T> addAction( final ActionLink<T> link, final ActionLink.ActionType type, final String entitlements) { return addAction(link, type, entitlements, false); } /** * Add item action (the given order is ignored. * * @param link action link. * @param type action type. * @param entitlements entitlements. * @param onConfirm specify TRUE to ask for confirmation. * @return current builder object. */ public Builder<T> addAction( final ActionLink<T> link, final ActionLink.ActionType type, final String entitlements, final boolean onConfirm) { actions.add(link, type, entitlements, onConfirm).hideLabel(); return this; } /** * Overridable method to generate field value rendering component. * * @param key field key. * @param bean source bean. * @return field rendering component. */ protected Component getValueComponent(final String key, final T bean) { LOG.debug("Processing field {}", key); Object value; try { value = PropertyResolver.getPropertyGetter(key, bean).invoke(bean); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { LOG.error("Error retrieving value for field {}", key, e); value = StringUtils.EMPTY; } LOG.debug("Field value {}", value); return value == null ? new Label("field", StringUtils.EMPTY) : new Label("field", new ResourceModel(value.toString(), value.toString())); } protected T getActualItem(final T item, final List<T> list) { return item == null ? null : IteratorUtils.find(list.iterator(), new Predicate<T>() { @Override public boolean evaluate(final T object) { return item.equals(object); } }); } @Override protected WizardMgtPanel<T> newInstance(final String id, final boolean wizardInModal) { return new ListViewPanel<T>( id, items, reference, includes, actions, check, reuseItem, wizardInModal, model) { private static final long serialVersionUID = 1L; @Override protected Component getValueComponent(final String key, final T bean) { return Builder.this.getValueComponent(key, bean); } @Override protected T getActualItem(final T item, final List<T> list) { return Builder.this.getActualItem(item, list); } @Override protected void customActionCallback(final AjaxRequestTarget target) { Builder.this.customActionCallback(target); } @Override protected void customActionOnFinishCallback(final AjaxRequestTarget target) { Builder.this.customActionOnFinishCallback(target); } @Override protected void customActionOnCancelCallback(final AjaxRequestTarget target) { Builder.this.customActionOnCancelCallback(target); } }; } protected void customActionCallback(final AjaxRequestTarget target) { } protected void customActionOnCancelCallback(final AjaxRequestTarget target) { } protected void customActionOnFinishCallback(final AjaxRequestTarget target) { } } @Override @SuppressWarnings("unchecked") public void onEvent(final IEvent<?> event) { if (event.getPayload() instanceof AjaxWizard.NewItemEvent) { final T item = ((AjaxWizard.NewItemEvent<T>) event.getPayload()).getItem(); final AjaxRequestTarget target = ((AjaxWizard.NewItemEvent<T>) event.getPayload()).getTarget(); if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) { final T old = getActualItem(item, ListViewPanel.this.listOfItems); int indexOf = ListViewPanel.this.listOfItems.size(); if (old != null) { indexOf = ListViewPanel.this.listOfItems.indexOf(old); ListViewPanel.this.listOfItems.remove(old); } ListViewPanel.this.listOfItems.add(indexOf, item); } target.add(ListViewPanel.this); super.onEvent(event); } else if (event.getPayload() instanceof ListViewPanel.ListViewReload) { final ListViewPanel.ListViewReload<?> payload = (ListViewPanel.ListViewReload<?>) event.getPayload(); if (payload.getItems() != null) { ListViewPanel.this.listOfItems.clear(); try { ListViewPanel.this.listOfItems.addAll((List<T>) payload.getItems()); } catch (RuntimeException re) { LOG.warn("Error reloading items", re); } } payload.getTarget().add(ListViewPanel.this); } else { super.onEvent(event); } } protected abstract T getActualItem(final T item, final List<T> list); public static class ListViewReload<T extends Serializable> implements Serializable { private static final long serialVersionUID = 1509151005816590312L; private final AjaxRequestTarget target; private final List<T> items; public ListViewReload(final AjaxRequestTarget target) { this.target = target; this.items = null; } public ListViewReload(final List<T> items, final AjaxRequestTarget target) { this.target = target; this.items = items; } public AjaxRequestTarget getTarget() { return target; } public List<T> getItems() { return items; } } }