/* * 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 ro.nextreports.server.web.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.Stack; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider; import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider; import org.apache.wicket.extensions.markup.html.repeater.tree.NestedTree; import org.apache.wicket.extensions.markup.html.repeater.tree.content.Folder; import org.apache.wicket.extensions.markup.html.repeater.tree.theme.WindowsTheme; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.spring.injection.annot.SpringBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ro.nextreports.server.StorageConstants; import ro.nextreports.server.domain.Entity; import ro.nextreports.server.exception.NotFoundException; import ro.nextreports.server.service.StorageService; import ro.nextreports.server.util.EntityComparator; import ro.nextreports.server.util.I18NUtil; import ro.nextreports.server.web.NextServerSession; import ro.nextreports.server.web.chart.ChartSection; import ro.nextreports.server.web.common.event.AjaxUpdateEvent; import ro.nextreports.server.web.common.event.AjaxUpdateListener; import ro.nextreports.server.web.common.event.BulkMenuUpdateEvent; import ro.nextreports.server.web.common.menu.MenuPanel; import ro.nextreports.server.web.common.table.AjaxCheckTablePanel; import ro.nextreports.server.web.common.table.FakeSortableDataAdapter; import ro.nextreports.server.web.common.table.SortableDataAdapter; import ro.nextreports.server.web.core.event.SelectEntityEvent; import ro.nextreports.server.web.core.menu.EntityBulkMenuPanel; import ro.nextreports.server.web.core.menu.EntityMenuPanel; import ro.nextreports.server.web.core.menu.EntityTreeExpansionState; import ro.nextreports.server.web.core.menu.EntityTreeMenuPanel; import ro.nextreports.server.web.core.section.EntitySection; import ro.nextreports.server.web.core.section.SectionContext; import ro.nextreports.server.web.core.section.SectionContextUtil; import ro.nextreports.server.web.core.section.SectionManager; import ro.nextreports.server.web.core.table.ActionsColumn; import ro.nextreports.server.web.core.table.CreatedByColumn; import ro.nextreports.server.web.core.table.CreationDateColumn; import ro.nextreports.server.web.core.table.LastUpdatedByColumn; import ro.nextreports.server.web.core.table.LastUpdatedDateColumn; import ro.nextreports.server.web.core.table.NameColumn; import ro.nextreports.server.web.core.table.TypeColumn; import ro.nextreports.server.web.core.tree.EntityTreeProvider; import ro.nextreports.server.web.datasource.DataSourceSection; import ro.nextreports.server.web.report.ReportSection; import ro.nextreports.server.web.schedule.SchedulerSection; /** * @author Decebal Suiu */ public class EntityBrowserPanel extends StackPanel implements AjaxUpdateListener { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(EntityBrowserPanel.class); protected LocationPanel locationPanel; protected StatusBarPanel statusBarPanel; protected MenuPanel bulkMenuPanel; protected MenuPanel treeMenuPanel; protected MenuPanel menuPanel; protected EntityTree tree; protected ISortableDataProvider<Entity, String> dataProvider; protected AjaxCheckTablePanel<Entity> tablePanel; protected String sectionId; @SpringBean protected StorageService storageService; @SpringBean protected SectionManager sectionManager; public EntityBrowserPanel(String id, String sectionId) { super(id); this.sectionId = sectionId; setModel(new EntityBrowserModel(sectionId)); locationPanel = new LocationPanel("locationPanel", sectionId); locationPanel.setOutputMarkupId(true); add(locationPanel); statusBarPanel = new StatusBarPanel("statusBarPanel", sectionId); statusBarPanel.setOutputMarkupId(true); add(statusBarPanel); tree = createTree(getRootPath()); tree.setOutputMarkupId(true); add(tree); addTreeLinks(); dataProvider = getEntityDataProvider(); tablePanel = createTablePanel(dataProvider); tablePanel.setOutputMarkupId(true); initWorkspace(tablePanel); addTableLinks(); addBulkLinks(); } public void onAjaxUpdate(AjaxUpdateEvent event) { if (event instanceof SelectEntityEvent) { SelectEntityEvent selectEntityEvent = (SelectEntityEvent) event; selectEntity(selectEntityEvent.getEntity(), event.getTarget()); } else if (event instanceof BulkMenuUpdateEvent) { event.getTarget().add(bulkMenuPanel); event.getTarget().add(menuPanel); } } @Override protected void onBeforeRender() { selectEntity(getModelObject(), null); super.onBeforeRender(); } protected EntityTree createTree(String rootPath) { ITreeProvider<Entity> treeProvider = new EntityTreeProvider(rootPath) { private static final long serialVersionUID = 1L; @Override protected boolean acceptEntityAsChild(Entity entity) { return (entity instanceof ro.nextreports.server.domain.Folder); } @Override protected List<Entity> getChildren(String id) throws NotFoundException { // sort List<Entity> children = super.getChildren(id); Collections.sort(children, new EntityComparator()); return children; } }; return new EntityTree("tree", treeProvider, new EntityTreeStateModel()); } private class EntityTreeStateModel extends AbstractReadOnlyModel<Set<Entity>> { @Override public Set<Entity> getObject() { return EntityTreeExpansionState.get(); } } protected AjaxCheckTablePanel<Entity> createTablePanel(ISortableDataProvider<Entity, String> dataProvider) { return new AjaxCheckTablePanel<Entity>("work", createTableColumns(), dataProvider, getEntitiesPerPage()) { private static final long serialVersionUID = 1L; @Override protected Item<Entity> newRowTableItem(IModel<Entity> entityIModel, Item<Entity> item) { // select report from scheduler job action or dashboard GoToReport selectEntity(entityIModel.getObject(), item, ReportSection.ID); // select chart from dashboard GoTo selectEntity(entityIModel.getObject(), item, ChartSection.ID); // select data source from audit GoTo selectEntity(entityIModel.getObject(), item, DataSourceSection.ID); // select scheduler from audit GoTo selectEntity(entityIModel.getObject(), item, SchedulerSection.ID); return item; } }; } protected int getEntitiesPerPage() { return Integer.MAX_VALUE; } protected ISortableDataProvider<Entity, String> getEntityDataProvider() { return new SortableDataAdapter<Entity>(new EntityDataProvider(getModel())); } protected List<IColumn<Entity, String>> createTableColumns() { List<IColumn<Entity, String>> columns = new ArrayList<IColumn<Entity, String>>(); columns.add(new NameColumn() { private static final long serialVersionUID = 1L; @Override public void onEntitySelection(Entity entity, AjaxRequestTarget target) { selectEntity(entity, target); } }); columns.add(new ActionsColumn()); columns.add(new TypeColumn()); columns.add(new CreatedByColumn()); columns.add(new CreationDateColumn()); columns.add(new LastUpdatedByColumn()); columns.add(new LastUpdatedDateColumn()); return columns; } private void addBulkLinks() { bulkMenuPanel = new EntityBulkMenuPanel("bulkMenuPanel", tablePanel.getSelected(), sectionId); bulkMenuPanel.setOutputMarkupId(true); bulkMenuPanel.setOutputMarkupPlaceholderTag(true); add(bulkMenuPanel); } private void addTreeLinks() { treeMenuPanel = new EntityTreeMenuPanel("treeMenuPanel", tree); treeMenuPanel.setOutputMarkupId(true); add(treeMenuPanel); } private void addTableLinks() { menuPanel = new EntityMenuPanel("menuPanel", getModel(), sectionId) { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return !bulkMenuPanel.isVisible(); } }; menuPanel.setOutputMarkupPlaceholderTag(true); menuPanel.setOutputMarkupId(true); add(menuPanel); } private void onNodeClicked(Entity entity, AjaxRequestTarget target) { // set the new selected entity setCurrentPath(entity.getPath()); // TODO String count = "unknown"; try { if (dataProvider instanceof FakeSortableDataAdapter) { // right now only SecurityBrowserPanel uses pagination and there is no need for security on entities (users are seen by admin) count = String.valueOf(storageService.countEntityChildrenById(entity.getId())); } else { count = String.valueOf(dataProvider.size()); } } catch (NotFoundException ex) { LOG.error(ex.getMessage(), ex); } SectionContextUtil.setCurrentEntityChildren(sectionId, count); SectionContextUtil.setLookFor(sectionId, null); if (target != null) { target.add(tablePanel); target.add(locationPanel); target.add(bulkMenuPanel); target.add(menuPanel); target.add(statusBarPanel); } tablePanel.unselectAll(); restoreWorkspace(target); } protected void selectEntity(Entity entity, AjaxRequestTarget target) { if (entity == null) { entity = getRoot(); } if (LOG.isDebugEnabled()) { LOG.debug("Select entity '" + entity.getPath() + "'"); } tree.expandsParents(entity, target); if (target != null) { tree.updateBranch(entity, target); } onNodeClicked(entity, target); } private String getCurrentPath() { return SectionContextUtil.getCurrentPath(sectionId); } private void setCurrentPath(String path) { SectionContextUtil.setCurrentPath(sectionId, path); } private Entity getCurrentEntity() { String path = getCurrentPath(); try { return storageService.getEntity(path); } catch (NotFoundException e) { throw new WicketRuntimeException(e); } } private String getRootPath() { return getSection().getRootPath(); } private EntitySection getSection() { return (EntitySection) sectionManager.getSection(sectionId); } private Entity getRoot() { try { return storageService.getEntity(getRootPath()); } catch (NotFoundException e) { throw new WicketRuntimeException(e); } } // select entity from GoTo actions private void selectEntity(Entity entity, Item<Entity> item, String sectionId) { SectionContext sectionContext = NextServerSession.get().getSectionContext(sectionId); if (sectionContext == null) { return; } String entityPath = SectionContextUtil.getSelectedEntityPath(sectionId); if ((entityPath != null) && entity.getPath().equals(entityPath)) { item.add(AttributeModifier.replace("class", "tr-selected")); // reset SectionContextUtil.setSelectedEntityPath(sectionId, null); } } class EntityTree extends NestedTree<Entity> { private static final long serialVersionUID = 1L; public EntityTree(String id, ITreeProvider<Entity> provider, IModel<Set<Entity>> state) { super(id, provider, state); add(new WindowsTheme()); } @Override protected Component newContentComponent(String id, IModel<Entity> model) { return new Folder<Entity>(id, this, model) { private static final long serialVersionUID = 1L; @Override protected boolean isClickable() { return true; } @Override protected void onClick(AjaxRequestTarget target) { // I don't want the default behavior (collapse node if it's expanded) // super.onClick(target); Entity entity = getModelObject(); if (tree.getState(entity) == State.COLLAPSED) { tree.expand(entity); } else { tree.updateNode(entity, target); } // unselect the (old) selected entity (force repaint) tree.updateNode(getCurrentEntity(), target); onNodeClicked(entity, target); } @Override protected String getOtherStyleClass(Entity t) { return getClosedStyleClass(); } @Override protected boolean isSelected() { return getCurrentPath().equals(getModelObject().getPath()); } @Override protected IModel<?> newLabelModel(IModel<Entity> model) { return Model.of(getNodeLabel(model.getObject())); } }; } protected String getNodeLabel(Entity entity) { if (I18NUtil.nodeNeedsInternationalization(entity.getName())) { return getString("node."+ entity.getName()); } return entity.getName(); } protected void expandsParents(Entity entity, AjaxRequestTarget target) { String path = entity.getPath(); String[] tokens = path.split(StorageConstants.PATH_SEPARATOR); Entity root = getRoot(); Stack<Entity> stack = new Stack<Entity>(); stack.add(root); String rootPath = root.getPath(); String[] rootTokens = rootPath.split(StorageConstants.PATH_SEPARATOR); Entity tmp = root; for (int i = rootTokens.length; i < tokens.length; i++) { tmp = getChild(tmp, tokens[i]); stack.add(tmp); } for (Entity item : stack) { tree.expand(item); tree.updateNode(item, target); } } private Entity getChild(Entity node, String name) { Entity[] children; try { children = storageService.getEntityChildren(node.getPath()); } catch (NotFoundException e) { throw new WicketRuntimeException(e); } for (Entity tmp : children) { if (name.equals(tmp.getName())) { return tmp; } } return null; } } }