Java tutorial
/* * Copyright 2011 Corpuslinguistic working group Humboldt University Berlin. * * Licensed 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 annis.gui.controlpanel; import annis.gui.AnnisUI; import annis.gui.CorpusBrowserPanel; import annis.gui.ExampleQueriesPanel; import annis.gui.MetaDataPanel; import annis.gui.filter.SetFilter; import annis.libgui.Background; import annis.libgui.CorpusSet; import annis.libgui.Helper; import annis.libgui.IDGenerator; import annis.libgui.InstanceConfig; import annis.security.UserConfig; import annis.service.objects.AnnisCorpus; import com.google.common.collect.Sets; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; import com.vaadin.data.Container; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.util.BeanContainer; import com.vaadin.data.util.filter.SimpleStringFilter; import com.vaadin.event.Action; import com.vaadin.event.FieldEvents; import com.vaadin.event.ItemClickEvent; import com.vaadin.server.FontAwesome; import com.vaadin.server.Resource; import static com.vaadin.server.Sizeable.UNITS_EM; import com.vaadin.server.VaadinSession; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.ui.AbstractSelect; import com.vaadin.ui.Alignment; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.ComboBox; import com.vaadin.ui.Component; import com.vaadin.ui.GridLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Notification; import com.vaadin.ui.Notification.Type; import com.vaadin.ui.ProgressBar; import com.vaadin.ui.Table; import com.vaadin.ui.TextField; import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.ui.themes.ValoTheme; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.ws.rs.core.Response; import org.slf4j.LoggerFactory; /** * * @author thomas */ public class CorpusListPanel extends VerticalLayout implements AbstractSelect.NewItemHandler, Action.Handler { private static final org.slf4j.Logger log = LoggerFactory.getLogger(CorpusListPanel.class); private static final Resource INFO_ICON = FontAwesome.INFO_CIRCLE; private static final Resource DOC_ICON = FontAwesome.FILE_TEXT_O; public static final String ALL_CORPORA = "All"; // holds the panels of auto generated queries private final ExampleQueriesPanel autoGenQueries; private final AnnisUI ui; public enum ActionType { Add, Remove }; private final ProgressBar pbLoadCorpora; private Table tblCorpora; private final HorizontalLayout selectionLayout; private final ComboBox cbSelection; private final TextField txtFilter; private transient UserConfig userConfig; private final InstanceConfig instanceConfig; private Container.Filter textFilter; private Container.Filter corpusSetFilter; public CorpusListPanel(InstanceConfig instanceConfig, ExampleQueriesPanel autoGenQueries, final AnnisUI ui) { this.instanceConfig = instanceConfig; this.autoGenQueries = autoGenQueries; this.ui = ui; final CorpusListPanel finalThis = this; setSizeFull(); selectionLayout = new HorizontalLayout(); selectionLayout.setWidth("100%"); selectionLayout.setHeight("-1px"); selectionLayout.setVisible(false); Label lblVisible = new Label("Visible: "); lblVisible.setSizeUndefined(); selectionLayout.addComponent(lblVisible); cbSelection = new ComboBox(); cbSelection.setDescription("Choose corpus selection set"); cbSelection.setWidth("100%"); cbSelection.setHeight("-1px"); cbSelection.addStyleName(ValoTheme.COMBOBOX_SMALL); cbSelection.setInputPrompt("Add new corpus selection set"); cbSelection.setNullSelectionAllowed(false); cbSelection.setNewItemsAllowed(true); cbSelection.setNewItemHandler((AbstractSelect.NewItemHandler) this); cbSelection.setImmediate(true); cbSelection.addValueChangeListener(new ValueChangeListener() { @Override public void valueChange(ValueChangeEvent event) { updateCorpusTable(); updateAutoGeneratedQueriesPanel(); } }); selectionLayout.addComponent(cbSelection); selectionLayout.setExpandRatio(cbSelection, 1.0f); selectionLayout.setSpacing(true); selectionLayout.setComponentAlignment(cbSelection, Alignment.MIDDLE_RIGHT); selectionLayout.setComponentAlignment(lblVisible, Alignment.MIDDLE_LEFT); addComponent(selectionLayout); txtFilter = new TextField(); txtFilter.setVisible(false); txtFilter.setInputPrompt("Filter"); txtFilter.setImmediate(true); txtFilter.setTextChangeTimeout(500); txtFilter.addTextChangeListener(new FieldEvents.TextChangeListener() { @Override public void textChange(FieldEvents.TextChangeEvent event) { BeanContainer<String, AnnisCorpus> availableCorpora = ui.getQueryState().getAvailableCorpora(); if (textFilter != null) { // remove the old filter availableCorpora.removeContainerFilter(textFilter); textFilter = null; } if (event.getText() != null && !event.getText().isEmpty()) { Set<String> selectedIDs = ui.getQueryState().getSelectedCorpora().getValue(); textFilter = new SimpleStringFilter("name", event.getText(), true, false); availableCorpora.addContainerFilter(textFilter); // select the first item List<String> filteredIDs = availableCorpora.getItemIds(); Set<String> selectedAndFiltered = new HashSet<>(selectedIDs); selectedAndFiltered.retainAll(filteredIDs); Set<String> selectedAndOutsideFilter = new HashSet<>(selectedIDs); selectedAndOutsideFilter.removeAll(filteredIDs); for (String id : selectedAndOutsideFilter) { tblCorpora.unselect(id); } if (selectedAndFiltered.isEmpty() && !filteredIDs.isEmpty()) { for (String id : selectedIDs) { tblCorpora.unselect(id); } tblCorpora.select(filteredIDs.get(0)); } } } }); txtFilter.setWidth("100%"); txtFilter.setHeight("-1px"); txtFilter.addStyleName(ValoTheme.TEXTFIELD_SMALL); addComponent(txtFilter); pbLoadCorpora = new ProgressBar(); pbLoadCorpora.setCaption("Loading corpus list..."); pbLoadCorpora.setIndeterminate(true); addComponent(pbLoadCorpora); tblCorpora = new Table(); addComponent(tblCorpora); tblCorpora.setVisible(false); // don't show list before it was not loaded tblCorpora.setContainerDataSource(ui.getQueryState().getAvailableCorpora()); tblCorpora.setMultiSelect(true); tblCorpora.setPropertyDataSource(ui.getQueryState().getSelectedCorpora()); tblCorpora.addGeneratedColumn("info", new InfoGenerator()); tblCorpora.addGeneratedColumn("docs", new DocLinkGenerator()); tblCorpora.setVisibleColumns("name", "textCount", "tokenCount", "info", "docs"); tblCorpora.setColumnHeaders("Name", "Texts", "Tokens", "", ""); tblCorpora.setHeight("100%"); tblCorpora.setWidth("100%"); tblCorpora.setSelectable(true); tblCorpora.setNullSelectionAllowed(false); tblCorpora.setColumnExpandRatio("name", 0.6f); tblCorpora.setColumnExpandRatio("textCount", 0.15f); tblCorpora.setColumnExpandRatio("tokenCount", 0.25f); tblCorpora.addStyleName(ValoTheme.TABLE_SMALL); tblCorpora.addActionHandler((Action.Handler) this); tblCorpora.setImmediate(true); tblCorpora.addItemClickListener(new ItemClickEvent.ItemClickListener() { @Override public void itemClick(ItemClickEvent event) { Set selections = (Set) tblCorpora.getValue(); if (selections.size() == 1 && event.isCtrlKey() && tblCorpora.isSelected(event.getItemId())) { tblCorpora.setValue(null); } } }); tblCorpora.setItemDescriptionGenerator(new TooltipGenerator()); tblCorpora.addValueChangeListener(new CorpusTableChangedListener(finalThis)); Button btReload = new Button(); btReload.addClickListener(new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { updateCorpusSetList(false, false); Notification.show("Reloaded corpus list", Notification.Type.HUMANIZED_MESSAGE); } }); btReload.setIcon(FontAwesome.REFRESH); btReload.setDescription("Reload corpus list"); btReload.addStyleName(ValoTheme.BUTTON_ICON_ONLY); selectionLayout.addComponent(btReload); selectionLayout.setComponentAlignment(btReload, Alignment.MIDDLE_RIGHT); tblCorpora.setSortContainerPropertyId("name"); setExpandRatio(tblCorpora, 1.0f); updateCorpusSetList(true, true); } @Override public void attach() { super.attach(); IDGenerator.assignIDForFields(CorpusListPanel.this, tblCorpora); } public void updateCorpusSetList(boolean scrollToSelected) { updateCorpusSetList(false, scrollToSelected); } private void updateCorpusSetList(boolean showLoginMessage, boolean scrollToSelected) { if (ui != null) { ui.clearCorpusConfigCache(); } CorpusListUpdater updater = new CorpusListUpdater(showLoginMessage, scrollToSelected); Background.run(updater); } private void updateCorpusTable() { String selectedCorpusSetName = (String) cbSelection.getValue(); // first reset the corpus set filter if (corpusSetFilter != null) { ui.getQueryState().getAvailableCorpora().removeContainerFilter(corpusSetFilter); corpusSetFilter = null; } if (userConfig != null && selectedCorpusSetName != null && !ALL_CORPORA.equals(selectedCorpusSetName)) { CorpusSet selectedCS = null; // TODO: use map List<CorpusSet> corpusSets = new LinkedList<>(); if (instanceConfig != null && instanceConfig.getCorpusSets() != null) { corpusSets.addAll(instanceConfig.getCorpusSets()); } if (userConfig.getCorpusSets() != null) { corpusSets.addAll(userConfig.getCorpusSets()); } for (CorpusSet cs : corpusSets) { if (cs.getName().equals(selectedCorpusSetName)) { selectedCS = cs; } } if (selectedCS != null) { Set<String> corpusNamesByCorpusSet = new HashSet<>(selectedCS.getCorpora()); List<String> allCorpusNames = ui.getQueryState().getAvailableCorpora().getItemIds(); corpusNamesByCorpusSet.retainAll(allCorpusNames); corpusSetFilter = new SetFilter(corpusNamesByCorpusSet, "name"); ui.getQueryState().getAvailableCorpora().addContainerFilter(corpusSetFilter); } } tblCorpora.sort(); } /** * Updates or initializes the panel, which holds the automatic generated * queries. */ private void updateAutoGeneratedQueriesPanel() { // make sure to make a copy since we are adding items to this set Set<String> corpora = new HashSet<>(ui.getQueryState().getSelectedCorpora().getValue()); if (corpora.isEmpty()) { corpora.addAll(ui.getQueryState().getAvailableCorpora().getItemIds()); } autoGenQueries.setSelectedCorpusInBackground(corpora); } private List<AnnisCorpus> getCorpusListFromServer() { List<AnnisCorpus> result = new LinkedList<>(); try { WebResource rootRes = Helper.getAnnisWebResource(); result = rootRes.path("query").path("corpora").get(new AnnisCorpusListType()); return result; } catch (ClientHandlerException ex) { log.error(null, ex); Notification.show("Service not available: " + ex.getLocalizedMessage(), Notification.Type.TRAY_NOTIFICATION); } catch (UniformInterfaceException ex) { if (ex.getResponse().getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) { Notification.show("You are not authorized to get the corpus list.", ex.getMessage(), Notification.Type.WARNING_MESSAGE); } else if (ex.getResponse().getStatus() == Response.Status.FORBIDDEN.getStatusCode()) { Notification.show("Your account has expired.", ex.getMessage(), Notification.Type.WARNING_MESSAGE); return result; } else { log.error(null, ex); Notification.show("Remote exception: " + ex.getLocalizedMessage(), Notification.Type.TRAY_NOTIFICATION); } } return null; } private UserConfig getUserConfigFromRemote() { WebResource rootRes = Helper.getAnnisWebResource(); // get the current corpus configuration return rootRes.path("admin").path("userconfig").get(UserConfig.class); } private void storeChangesRemote() { WebResource rootRes = Helper.getAnnisWebResource(); // store the config on the server rootRes.path("admin").path("userconfig").post(this.userConfig); } @Override public void addNewItem(String newItemCaption) { if (!cbSelection.containsId(newItemCaption) && this.userConfig != null) { cbSelection.addItem(newItemCaption); cbSelection.setValue(newItemCaption); try { UserConfig newUserConfig = getUserConfigFromRemote(); this.userConfig = newUserConfig; // add new corpus set to the config CorpusSet newSet = new CorpusSet(); newSet.setName(newItemCaption); this.userConfig.getCorpusSets().add(newSet); // store the config on the server storeChangesRemote(); // update everything else updateCorpusTable(); } catch (ClientHandlerException ex) { log.error("could not store new corpus set", ex); Notification.show("Could not store new corpus set: " + ex.getLocalizedMessage(), Type.WARNING_MESSAGE); } catch (UniformInterfaceException ex) { if (ex.getResponse().getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) { log.error(ex.getLocalizedMessage()); Notification.show("Not allowed", "you have not the permission to add a new corpus group", Type.WARNING_MESSAGE); } else { log.error("error occures while storing new corpus set", ex); Notification.show("error occures while storing new corpus set", "Maybe you will have to log in", Type.WARNING_MESSAGE); } } } // end if new item } @Override public Action[] getActions(Object target, Object sender) { String corpusName = (String) target; LinkedList<Action> result = new LinkedList<>(); if (target == null) { // no action for empty space return new Action[0]; } if (Helper.getUser() == null) { // we can't change anything if we are not logged in so don't even try return new Action[0]; } if (userConfig != null) { for (CorpusSet entry : userConfig.getCorpusSets()) { if (entry.getCorpora().contains(corpusName)) { AddRemoveAction action = new AddRemoveAction(ActionType.Remove, entry, corpusName, "Remove from " + entry.getName()); // add possibility to remove result.add(action); } else { AddRemoveAction action = new AddRemoveAction(ActionType.Add, entry, corpusName, "Add to " + entry.getName()); // add possibility to add result.add(action); } } } return result.toArray(new Action[result.size()]); } @Override public void handleAction(Action action, Object sender, Object target) { if (action instanceof AddRemoveAction && this.userConfig != null) { AddRemoveAction a = (AddRemoveAction) action; int idx = this.userConfig.getCorpusSets().indexOf(a.getCorpusSet()); if (idx > -1) { CorpusSet set = this.userConfig.getCorpusSets().get(idx); if (a.type == ActionType.Remove) { set.getCorpora().remove(a.getCorpusId()); if (set.getCorpora().isEmpty()) { // remove the set itself when it gets empty this.userConfig.getCorpusSets().remove(set); cbSelection.removeItem(a.getCorpusSet().getName()); cbSelection.select(ALL_CORPORA); } } else if (a.type == ActionType.Add) { set.getCorpora().add(a.getCorpusId()); } storeChangesRemote(); // update view updateCorpusTable(); } } } public void scrollToSelectedCorpus() { Set<String> corpora = ui.getQueryState().getSelectedCorpora().getValue(); if (tblCorpora != null) { if (!corpora.isEmpty() && ui.getQueryState().getAvailableCorpora().size() > 0) { String firstCorpusName = corpora.iterator().next(); int idx = ui.getQueryState().getAvailableCorpora().indexOfId(firstCorpusName); if (idx >= 0) { tblCorpora.setCurrentPageFirstItemIndex(idx); } } } } /** * Set the currently displayed corpus set. * * @param corpusSet */ public void setCorpusSet(String corpusSet) { cbSelection.select(corpusSet); } public class DocLinkGenerator implements Table.ColumnGenerator { @Override public Object generateCell(Table source, Object itemId, Object columnId) { final String id = (String) itemId; if (ui.getSearchView().getDocBrowserController().docsAvailable(id)) { Button l = new Button(); l.setStyleName(ValoTheme.BUTTON_BORDERLESS); l.setIcon(DOC_ICON); l.setDescription("opens the document browser for " + id); l.addClickListener(new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { ui.getSearchView().getDocBrowserController().openDocBrowser(id); } }); return l; } return ""; } } public class InfoGenerator implements Table.ColumnGenerator { @Override public Component generateCell(Table source, Object itemId, Object columnId) { final String id = (String) itemId; final Button l = new Button(); l.addStyleName(ValoTheme.BUTTON_BORDERLESS); l.setIcon(INFO_ICON); l.setDescription("show metadata and annotations for " + id); l.addClickListener(new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { if (ui.getQueryController() != null) { l.setEnabled(false); initCorpusBrowser(id, l); } } }); return l; } } public static class AddRemoveAction extends Action { private ActionType type; private CorpusSet corpusSet; private String corpusId; public AddRemoveAction(ActionType type, CorpusSet corpusSet, String corpusId, String caption) { super(caption); this.type = type; this.corpusSet = corpusSet; this.corpusId = corpusId; } public ActionType getType() { return type; } public String getCorpusId() { return corpusId; } public CorpusSet getCorpusSet() { return corpusSet; } } private class CorpusTableChangedListener implements ValueChangeListener { private final CorpusListPanel finalThis; public CorpusTableChangedListener(CorpusListPanel finalThis) { this.finalThis = finalThis; } @Override public void valueChange(ValueChangeEvent event) { // make sure the selected corpus set does contain the corpus Set<String> corpora = ui.getQueryState().getSelectedCorpora().getValue(); Set<String> visibleCorpora = new HashSet<>(ui.getQueryState().getAvailableCorpora().getItemIds()); if (!visibleCorpora.containsAll(corpora)) { setCorpusSet(CorpusListPanel.ALL_CORPORA); } updateAutoGeneratedQueriesPanel(); ui.getQueryController().corpusSelectionChangedInBackground(); } } private static class AnnisCorpusListType extends GenericType<List<AnnisCorpus>> { public AnnisCorpusListType() { } } public Table getTblCorpora() { return tblCorpora; } public void initCorpusBrowser(String topLevelCorpusName, final Button l) { AnnisCorpus c = ui.getQueryState().getAvailableCorpora().getItem(topLevelCorpusName).getBean(); MetaDataPanel meta = new MetaDataPanel(c.getName()); CorpusBrowserPanel browse = new CorpusBrowserPanel(c, ui.getQueryController()); GridLayout infoLayout = new GridLayout(2, 2); infoLayout.setSizeFull(); String corpusURL = Helper.generateCorpusLink(Sets.newHashSet(topLevelCorpusName)); Label lblLink = new Label("Link to corpus: <a href=\"" + corpusURL + "\">" + corpusURL + "</a>", ContentMode.HTML); lblLink.setHeight("-1px"); lblLink.setWidth("-1px"); infoLayout.addComponent(meta, 0, 0); infoLayout.addComponent(browse, 1, 0); infoLayout.addComponent(lblLink, 0, 1, 1, 1); infoLayout.setRowExpandRatio(0, 1.0f); infoLayout.setColumnExpandRatio(0, 0.5f); infoLayout.setColumnExpandRatio(1, 0.5f); infoLayout.setComponentAlignment(lblLink, Alignment.MIDDLE_CENTER); Window window = new Window("Corpus information for " + c.getName() + " (ID: " + c.getId() + ")", infoLayout); window.setWidth(70, UNITS_EM); window.setHeight(45, UNITS_EM); window.setResizable(true); window.setModal(false); window.setResizeLazy(true); window.addCloseListener(new Window.CloseListener() { @Override public void windowClose(Window.CloseEvent e) { l.setEnabled(true); } }); UI.getCurrent().addWindow(window); window.center(); } public static class TooltipGenerator implements AbstractSelect.ItemDescriptionGenerator { @Override public String generateDescription(Component source, Object itemId, Object propertyId) { if ("name".equals(propertyId)) { return (String) itemId; } return null; } } private class CorpusListUpdater implements Runnable { private final boolean showLoginMessage; private final boolean scrollToSelected; public CorpusListUpdater(boolean showLoginMessage, boolean scrollToSelected) { this.showLoginMessage = showLoginMessage; this.scrollToSelected = scrollToSelected; } @Override public void run() { // query in background try { final UserConfig newUserConfig = getUserConfigFromRemote(); final List<AnnisCorpus> newCorpusList = getCorpusListFromServer(); // update the GUI ui.access(new Runnable() { @Override public void run() { if (newUserConfig != null && newCorpusList != null) { ui.getQueryState().getAvailableCorpora().removeAllItems(); ui.getQueryState().getAvailableCorpora().addAll(newCorpusList); userConfig = newUserConfig; if (VaadinSession.getCurrent().getAttribute(AnnisCorpus.class) == null) { if (showLoginMessage) { if (newCorpusList.isEmpty()) { Notification.show( "No corpora found. Please login " + "(use button at upper right corner) to see more corpora.", Notification.Type.HUMANIZED_MESSAGE); } } } Object oldSelection = cbSelection.getValue(); cbSelection.removeAllItems(); cbSelection.addItem(ALL_CORPORA); List<CorpusSet> corpusSets = new LinkedList<>(); if (instanceConfig != null && instanceConfig.getCorpusSets() != null) { corpusSets.addAll(instanceConfig.getCorpusSets()); } if (userConfig.getCorpusSets() != null) { corpusSets.addAll(userConfig.getCorpusSets()); } // add the corpus set names in sorted order TreeSet<String> corpusSetNames = new TreeSet<>(); for (CorpusSet cs : corpusSets) { corpusSetNames.add(cs.getName()); } for (String s : corpusSetNames) { cbSelection.addItem(s); } // restore old selection or select the ALL corpus selection if (oldSelection != null && cbSelection.containsId(oldSelection)) { cbSelection.select(oldSelection); } else { if (instanceConfig != null && instanceConfig.getDefaultCorpusSet() != null && instanceConfig.getDefaultCorpusSet().length() > 0) { cbSelection.select(instanceConfig.getDefaultCorpusSet()); } else { cbSelection.select(ALL_CORPORA); } } updateCorpusTable(); updateAutoGeneratedQueriesPanel(); if (scrollToSelected) { scrollToSelectedCorpus(); } } // end if querying the server for corpus list was successful } }); } catch (Throwable ex) { log.warn("Could not get corpus list", ex); } finally { ui.access(new Runnable() { @Override public void run() { tblCorpora.setVisible(true); txtFilter.setVisible(true); selectionLayout.setVisible(true); pbLoadCorpora.setVisible(false); } }); } } } }