/*
* 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.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.model.Model;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.filter.Filter;
import org.apache.isis.applib.filter.Filters;
import org.apache.isis.applib.layout.component.Grid;
import org.apache.isis.applib.services.tablecol.TableColumnOrderService;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
import org.apache.isis.core.metamodel.facets.object.grid.GridFacet;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.Contributed;
import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
import org.apache.isis.viewer.wicket.model.common.OnConcurrencyExceptionHandler;
import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
import org.apache.isis.viewer.wicket.ui.components.collection.bulk.BulkActionsProvider;
import org.apache.isis.viewer.wicket.ui.components.collection.count.CollectionCountProvider;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ColumnAbstract;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterPropertyColumn;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterTitleColumn;
import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterToggleboxColumn;
import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
/**
* {@link PanelAbstract Panel} that represents a {@link EntityCollectionModel
* collection of entity}s rendered using {@link AjaxFallbackDefaultDataTable}.
*/
public class CollectionContentsAsAjaxTablePanel
extends PanelAbstract<EntityCollectionModel> implements CollectionCountProvider {
private static final long serialVersionUID = 1L;
private static final String ID_TABLE = "table";
private IsisAjaxFallbackDataTable<ObjectAdapter,String> dataTable;
public CollectionContentsAsAjaxTablePanel(final String id, final EntityCollectionModel model) {
super(id, model);
}
@Override
protected void onInitialize() {
super.onInitialize();
buildGui();
}
private void buildGui() {
final List<IColumn<ObjectAdapter,String>> columns = Lists.newArrayList();
// bulkactions
final BulkActionsProvider bulkActionsProvider = getBulkActionsProvider();
ObjectAdapterToggleboxColumn toggleboxColumn = null;
if(bulkActionsProvider != null) {
toggleboxColumn = bulkActionsProvider.createToggleboxColumn();
if(toggleboxColumn != null) {
columns.add(toggleboxColumn);
}
bulkActionsProvider.configureBulkActions(toggleboxColumn);
}
final EntityCollectionModel model = getModel();
addTitleColumn(columns, model.getParentObjectAdapterMemento(), getSettings().getMaxTitleLengthInStandaloneTables(), getSettings().getMaxTitleLengthInStandaloneTables());
addPropertyColumnsIfRequired(columns);
final CollectionContentsSortableDataProvider dataProvider = new CollectionContentsSortableDataProvider(model);
dataTable = new IsisAjaxFallbackDataTable<>(ID_TABLE, columns, dataProvider, model.getPageSize(), toggleboxColumn);
addOrReplace(dataTable);
dataTable.honourHints();
if(toggleboxColumn != null) {
final OnConcurrencyExceptionHandler handler2 = new OnConcurrencyExceptionHandler() {
private static final long serialVersionUID = 1L;
@Override
public void onConcurrencyException(
final Component context,
final ObjectAdapter selectedAdapter,
final ConcurrencyException ex,
final AjaxRequestTarget ajaxRequestTarget) {
// this causes the row to be repainted
// but it isn't possible (yet) to raise any warning
// because that only gets flushed on page refresh.
//
// perhaps something to tackle in a separate ticket....
ajaxRequestTarget.add(dataTable);
}
};
toggleboxColumn.setOnConcurrencyExceptionHandler(handler2);
}
}
private BulkActionsProvider getBulkActionsProvider() {
Component component = this;
while(component != null) {
if(component instanceof BulkActionsProvider) {
return (BulkActionsProvider) component;
}
component = component.getParent();
}
return null;
}
private void addTitleColumn(
final List<IColumn<ObjectAdapter,String>> columns,
final ObjectAdapterMemento parentAdapterMementoIfAny,
final int maxTitleParented,
final int maxTitleStandalone) {
final int maxTitleLength = getModel().isParented()? maxTitleParented: maxTitleStandalone;
columns.add(new ObjectAdapterTitleColumn(parentAdapterMementoIfAny, maxTitleLength));
}
private void addPropertyColumnsIfRequired(final List<IColumn<ObjectAdapter,String>> columns) {
final ObjectSpecification typeOfSpec = getModel().getTypeOfSpecification();
// same code also appears in EntityPage.
// we need to do this here otherwise any tables will render the columns in the wrong order until at least
// one object of that type has been rendered via EntityPage.
final GridFacet gridFacet = typeOfSpec.getFacet(GridFacet.class);
if(gridFacet != null) {
// the facet should always exist, in fact
// just enough to ask for the metadata.
// This will cause the current ObjectSpec to be updated as a side effect.
final Grid unused = gridFacet.getGrid();
}
final Where whereContext =
getModel().isParented()
? Where.PARENTED_TABLES
: Where.STANDALONE_TABLES;
final ObjectSpecification parentSpecIfAny =
getModel().isParented()
? getModel().getParentObjectAdapterMemento().getObjectAdapter(ConcurrencyChecking.NO_CHECK,
getPersistenceSession(), getSpecificationLoader()).getSpecification()
: null;
@SuppressWarnings("unchecked")
final Filter<ObjectAssociation> filter = Filters.and(
ObjectAssociation.Filters.PROPERTIES,
ObjectAssociation.Filters.staticallyVisible(whereContext),
associationDoesNotReferenceParent(parentSpecIfAny));
final List<? extends ObjectAssociation> propertyList = typeOfSpec.getAssociations(Contributed.INCLUDED, filter);
final Map<String, ObjectAssociation> propertyById = Maps.newLinkedHashMap();
for (final ObjectAssociation property : propertyList) {
propertyById.put(property.getId(), property);
}
List<String> propertyIds = Lists.newArrayList(propertyById.keySet());
// optional SPI to reorder
final List<TableColumnOrderService> tableColumnOrderServices =
getServicesInjector().lookupServices(TableColumnOrderService.class);
for (final TableColumnOrderService tableColumnOrderService : tableColumnOrderServices) {
final List<String> propertyReorderedIds = reordered(tableColumnOrderService, propertyIds);
if(propertyReorderedIds != null) {
propertyIds = propertyReorderedIds;
break;
}
}
for (final String propertyId : propertyIds) {
final ObjectAssociation property = propertyById.get(propertyId);
final ColumnAbstract<ObjectAdapter> nopc = createObjectAdapterPropertyColumn(property);
columns.add(nopc);
}
}
private List<String> reordered(
final TableColumnOrderService tableColumnOrderService,
final List<String> propertyIds) {
final Class<?> collectionType = getModel().getTypeOfSpecification().getCorrespondingClass();
final ObjectAdapterMemento parentObjectAdapterMemento = getModel().getParentObjectAdapterMemento();
if(parentObjectAdapterMemento != null) {
final ObjectAdapter parentObjectAdapter = parentObjectAdapterMemento
.getObjectAdapter(ConcurrencyChecking.NO_CHECK, getPersistenceSession(), getSpecificationLoader());
final Object parent = parentObjectAdapter.getObject();
final String collectionId = getModel().getCollectionMemento().getId();
return tableColumnOrderService.orderParented(parent, collectionId, collectionType, propertyIds);
} else {
return tableColumnOrderService.orderStandalone(collectionType, propertyIds);
}
}
static Filter<ObjectAssociation> associationDoesNotReferenceParent(final ObjectSpecification parentSpec) {
if(parentSpec == null) {
return Filters.any();
}
return new Filter<ObjectAssociation>() {
@Override
public boolean accept(ObjectAssociation association) {
final HiddenFacet facet = association.getFacet(HiddenFacet.class);
if(facet == null) {
return true;
}
if (facet.where() != Where.REFERENCES_PARENT) {
return true;
}
final ObjectSpecification assocSpec = association.getSpecification();
final boolean associationSpecIsOfParentSpec = parentSpec.isOfType(assocSpec);
final boolean isVisible = !associationSpecIsOfParentSpec;
return isVisible;
}
};
}
private ObjectAdapterPropertyColumn createObjectAdapterPropertyColumn(final ObjectAssociation property) {
final NamedFacet facet = property.getFacet(NamedFacet.class);
final boolean escaped = facet == null || facet.escaped();
return new ObjectAdapterPropertyColumn(Model.of(property.getName()), property.getId(), property.getId(), escaped);
}
@Override
protected void onModelChanged() {
buildGui();
}
@Override
public Integer getCount() {
final EntityCollectionModel model = getModel();
return model.getCount();
}
//region > dependencies
@com.google.inject.Inject
private WicketViewerSettings settings;
protected WicketViewerSettings getSettings() {
return settings;
}
protected ServicesInjector getServicesInjector() {
return getIsisSessionFactory().getServicesInjector();
}
//endregion
}