| /* |
| * 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.Collection; |
| import java.util.List; |
| import java.util.Optional; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.syncope.client.ui.commons.Constants; |
| import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormChoiceComponentUpdatingBehavior; |
| import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; |
| import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel; |
| import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; |
| import org.apache.syncope.client.ui.commons.wizards.AjaxWizard; |
| import org.apache.syncope.client.console.wizards.WizardMgtPanel; |
| import org.apache.wicket.AttributeModifier; |
| import org.apache.wicket.Component; |
| import org.apache.wicket.PageReference; |
| import org.apache.wicket.ajax.AjaxEventBehavior; |
| 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.WebMarkupContainer; |
| 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); |
| |
| private ActionLinksTogglePanel<T> togglePanel; |
| |
| 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 boolean captionVisible, |
| final IModel<? extends Collection<T>> model) { |
| |
| super(id, wizardInModal); |
| setOutputMarkupId(true); |
| |
| togglePanel = getTogglePanel(); |
| |
| this.check = Model.of(check); |
| |
| WebMarkupContainer captionContainer = new WebMarkupContainer("captionContainer"); |
| captionContainer.setOutputMarkupPlaceholderTag(true); |
| captionContainer.setVisible(captionVisible); |
| addInnerObject(captionContainer); |
| |
| Label caption = new Label("caption", new ResourceModel("listview.caption", StringUtils.EMPTY)); |
| captionContainer.add(caption); |
| |
| 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 : 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; |
| LOG.debug("Show fields {}", toBeIncluded); |
| } |
| |
| addInnerObject(header(toBeIncluded)); |
| |
| beans = new ListView<>("beans", listOfItems) { |
| |
| private static final long serialVersionUID = -9112553137618363167L; |
| |
| @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<>("fields", toBeIncluded) { |
| |
| private static final long serialVersionUID = -9112553137618363167L; |
| |
| @Override |
| protected void populateItem(final ListItem<String> fieldItem) { |
| fieldItem.add(getValueComponent(fieldItem.getModelObject(), bean)); |
| if (togglePanel != null) { |
| fieldItem.add(new AttributeModifier("style", "cursor: pointer;")); |
| fieldItem.add(new AjaxEventBehavior(Constants.ON_CLICK) { |
| |
| private static final long serialVersionUID = -9027652037484739586L; |
| |
| @Override |
| protected String findIndicatorId() { |
| return StringUtils.EMPTY; |
| } |
| |
| @Override |
| protected void onEvent(final AjaxRequestTarget target) { |
| togglePanel.toggleWithContent( |
| target, |
| actions.cloneWithLabels("actions", new Model<>(bean)), |
| bean); |
| } |
| }); |
| } |
| } |
| }; |
| |
| beanItem.add(fields); |
| |
| if (togglePanel == null) { |
| beanItem.add(actions.clone("actions", new Model<>(bean))); |
| } else { |
| beanItem.add(new ActionsPanel<>("actions", new Model<>(bean)).setVisible(false).setEnabled(false)); |
| } |
| } |
| }; |
| beans.setOutputMarkupId(true); |
| beans.setReuseItems(reuseItem); |
| beans.setRenderBodyOnly(true); |
| checkGroup.add(beans); |
| } |
| |
| private static ListView<String> header(final List<String> labels) { |
| return new ListView<>("names", labels) { |
| |
| private static final long serialVersionUID = -9112553137618363167L; |
| |
| @Override |
| protected void populateItem(final ListItem<String> item) { |
| item.add(new Label(Constants.NAME_FIELD_NAME, |
| new ResourceModel(item.getModelObject(), item.getModelObject()))); |
| } |
| }; |
| } |
| |
| /** |
| * Use this to refresh the ListView with updated items (e.g. from callback methods) |
| * |
| * @param elements new items |
| */ |
| public void refreshList(final List<T> elements) { |
| beans.setList(elements); |
| } |
| |
| public void setCheckAvailability(final CheckAvailability check) { |
| // used to perform selectable enabling check condition |
| this.check.setObject(check); |
| |
| Optional<AjaxRequestTarget> target = RequestCycle.get().find(AjaxRequestTarget.class); |
| if (target.isPresent()) { |
| // reload group selector |
| target.get().add( |
| groupSelector.setVisible(check == CheckAvailability.AVAILABLE), groupSelector.getMarkupId()); |
| // reload the list view panel |
| target.get().add(ListViewPanel.this, getMarkupId()); |
| } |
| } |
| |
| protected abstract Component getValueComponent(String key, 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(List.of()); |
| |
| 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 boolean captionVisible = 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<>("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 Builder<T> setReuseItem(final boolean reuseItem) { |
| this.reuseItem = reuseItem; |
| return this; |
| } |
| |
| public Builder<T> setCaptionVisible(final boolean captionVisible) { |
| this.captionVisible = captionVisible; |
| return this; |
| } |
| |
| /** |
| * 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 Optional.ofNullable(value) |
| .map(o -> new Label("field", new ResourceModel(o.toString(), o.toString()))) |
| .orElseGet(() -> new Label("field", StringUtils.EMPTY)); |
| } |
| |
| protected T getActualItem(final T item, final List<T> list) { |
| return item == null |
| ? null |
| : list.stream().filter(item::equals).findAny().orElse(null); |
| } |
| |
| @Override |
| protected WizardMgtPanel<T> newInstance(final String id, final boolean wizardInModal) { |
| return new ListViewPanel<>( |
| id, items, reference, includes, actions, check, reuseItem, wizardInModal, captionVisible, model) { |
| |
| private static final long serialVersionUID = -1715389337530657988L; |
| |
| @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); |
| } |
| |
| @Override |
| protected ActionLinksTogglePanel<T> getTogglePanel() { |
| return Builder.this.getTogglePanel(); |
| } |
| }; |
| } |
| |
| protected ActionLinksTogglePanel<T> getTogglePanel() { |
| return null; |
| } |
| |
| 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 Optional<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.ifPresent(t -> t.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(T item, 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; |
| } |
| } |
| |
| protected ActionLinksTogglePanel<T> getTogglePanel() { |
| return null; |
| } |
| } |