Java tutorial
/* * Copyright (C) 2008-2013, fluid Operations AG * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.fluidops.iwb.widget.admin; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringEscapeUtils; import org.apache.log4j.Logger; import com.fluidops.ajax.FClientUpdate; import com.fluidops.ajax.FEvent; import com.fluidops.ajax.FEventListener; import com.fluidops.ajax.components.FButton; import com.fluidops.ajax.components.FCheckBox; import com.fluidops.ajax.components.FComponent; import com.fluidops.ajax.components.FContainer; import com.fluidops.ajax.components.FHTML; import com.fluidops.ajax.components.FLabel; import com.fluidops.ajax.components.FPopupWindow; import com.fluidops.ajax.components.FRadioButtonGroup; import com.fluidops.ajax.components.FSelectableTable; import com.fluidops.ajax.components.FTable; import com.fluidops.ajax.components.FTable.FilterPos; import com.fluidops.ajax.components.FTextArea; import com.fluidops.ajax.components.FTextInput2; import com.fluidops.ajax.components.ToolTip; import com.fluidops.ajax.helper.HtmlString; import com.fluidops.ajax.models.FSelectableTableModel; import com.fluidops.ajax.models.FSelectableTableModelImpl; import com.fluidops.iwb.ajax.FValue; import com.fluidops.iwb.api.EndpointImpl; import com.fluidops.iwb.api.WikiStorageBulkServiceImpl; import com.fluidops.iwb.api.WikiStorageBulkServiceImpl.*; import com.fluidops.iwb.model.ParameterConfigDoc; import com.fluidops.iwb.model.ParameterConfigDoc.Type; import com.fluidops.iwb.server.AbstractFileServlet; import com.fluidops.iwb.widget.AbstractWidget; import com.fluidops.iwb.widget.config.WidgetBaseConfig; import com.fluidops.iwb.wiki.Wikimedia; import com.fluidops.util.Rand; import com.google.common.collect.Sets; /** * Export functionality for wiki pages: the user can select from * the wiki pages and it is exported as a bootstrap. * * @author as */ public class WikiManagementWidget extends AbstractWidget<WikiManagementWidget.Config> { private static final Logger logger = Logger.getLogger(WikiManagementWidget.class.getName()); public static class Config extends WidgetBaseConfig { @ParameterConfigDoc(desc = "pre-applied filter for wiki pages", required = false) public FilterDescription filter; @ParameterConfigDoc(desc = "list of visible filters", type = Type.LIST, required = false) public List<FilterDescription> visibleFilters; @ParameterConfigDoc(desc = "list of visible controls", type = Type.LIST, required = false) public List<Control> visibleControls; } public enum FilterDescription { ALL, USERPAGES, BOOTSTRAP, IMPORTED, USERPAGES_SHADOWING_BOOTSTRAP, NAMESPACE, TEMPLATE; } public enum Control { DELETEREVISIONS, RESTORELATESTREVISION, EXPORT } @Override protected FComponent getComponent(String id) { Config config = get(); if (config == null) { config = new Config(); setConfig(config); } final WikiStorageBulkServiceImpl wsApi = new WikiStorageBulkServiceImpl(Wikimedia.getWikiStorage()); final WikiPageSelectionTable wpTable = new WikiPageSelectionTable(id, config, wsApi); wpTable.setNumberOfRows(20); wpTable.setFilterPos(FilterPos.TOP); wpTable.setShowCSVExport(true); wpTable.setEnableFilter(true); wpTable.setOverFlowContainer(true); // pre-apply configured filter Filter filter = getFilterFor(config.filter); if (filter != null) { wpTable.updateTableModelInternal(filter); } return wpTable; } @Override public String getTitle() { return "Wiki Management Widget"; } @Override public Class<?> getConfigClass() { return WikiManagementWidget.Config.class; } protected static FButton createExportButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("create", "Export") { @Override public void onClick() { try { String fileName = wsApi.createWikiBootstrap(wpTable.getSelectedObjects()); final FPopupWindow p = getPage() .getPopupWindowInstance("Export created successfully: " + fileName); p.add(createExportDownloadButton(fileName), "floatLeft"); p.addCloseButton("Close"); p.populateAndShow(); } catch (Exception e) { logger.warn("Error during wiki export: ", e); this.getPage().getPopupWindowInstance().showError(e.getMessage()); } } }; } protected static FButton createFilterBootstrapButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Show bootstrap only") { @Override public void onClick() { wpTable.updateTableModel(new WikiStorageBulkServiceImpl.FromBootstrapFilter()); } }; } protected static FButton createFilterTemplateButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Show template pages") { @Override public void onClick() { wpTable.updateTableModel(new TemplateFilter()); } }; } protected static FButton createFilterImportedButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Show imported only") { @Override public void onClick() { wpTable.updateTableModel(new WikiStorageBulkServiceImpl.ImportedFromWikibotFilter()); } }; } protected static FButton createFilterUserPagesShadowingBootstrapButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Show user pages shadowing a bootstrapped page") { @Override public void onClick() { wpTable.updateTableModel(new WikiStorageBulkServiceImpl.UserPageShadowsBootstrapFilter()); } }; } protected static FButton createFilterNoneButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Show all") { @Override public void onClick() { wpTable.updateTableModel(new WikiStorageBulkServiceImpl.VoidFilter()); } }; } protected static FButton createFilterUserpagesButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Show user pages only") { @Override public void onClick() { wpTable.updateTableModel(new WikiStorageBulkServiceImpl.NotFromBootstrapFilter()); } }; } protected static FButton createFilterNamespacePrefixButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Filter namespace") { @Override public void onClick() { final FTextInput2 input = new FTextInput2("inp", EndpointImpl.api().getNamespaceService().defaultNamespace()); FPopupWindow p = getPage().getPopupWindowInstance( "Please enter the namespace prefix to filter:<p>Hint: also namespaces can be used, e.g. Help"); Runnable okAction = new Runnable() { @Override public void run() { // check if a namespace prefix was entered, e.g. Help String prefix = input.getValue(); Map<String, String> m = EndpointImpl.api().getNamespaceService() .getRegisteredNamespacePrefixes(); if (m.containsKey(prefix)) prefix = m.get(prefix); wpTable.updateTableModel(new WikiStorageBulkServiceImpl.NamespacePrefixFilter(prefix)); } }; p.add(input); p.addButton("Ok", okAction); p.addCloseButton("Cancel"); p.populateAndShow(); } }; } protected static FButton createSelectPresetButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Select preset") { @Override public void onClick() { final FTextArea input = new FTextArea("inp"); input.cols = 35; input.rows = 10; StringBuilder currentPreset = new StringBuilder(); for (WikiPageMeta w : wpTable.getSelectedObjects()) { currentPreset.append(w.getPageUri().stringValue()).append("\n"); } input.value = currentPreset.toString(); FPopupWindow p = getPage().getPopupWindowInstance( "Please configure your preset by adding one valid wiki page URI per line:"); p.add(input); p.addButton("Ok", new Runnable() { @Override public void run() { Set<String> selectedPreset = Sets.newHashSet(input.getText().split("\r?\n")); List<WikiPageMeta> wm = wsApi .getAllWikipages(new WikiStorageBulkServiceImpl.StringSetFilter(selectedPreset)); // retrieve table model to select all FSelectableTableModel<WikiPageMeta> tm = wpTable.getModelSafe(); tm.setSelection(wm); wpTable.setSortColumn(0, FTable.SORT_DESCENDING); wpTable.populateView(); // the preset contains some invalid url, inform user List<WikiPageMeta> selectedAfter = wpTable.getSelectedObjects(); if (selectedPreset.size() != selectedAfter.size()) { for (WikiPageMeta w : selectedAfter) selectedPreset.remove(w.getPageUri().stringValue()); throw new IllegalStateException( "Preset contains URIs that are not known to the system: " + StringEscapeUtils.escapeHtml(selectedPreset.toString()) + ". Please check your preset."); } } }); p.addCloseButton("Cancel"); p.populateAndShow(); } }; } /** * Create a download button which redirects to the file servlet. The fileName * corresponds the name of the bootstrap zipfile, e.g. wikiBootstrapTIMESTAMP.zip * @param fileName * @return */ protected static FButton createExportDownloadButton(final String fileName) { return new FButton("b" + Rand.getIncrementalFluidUUID(), "Download") { @Override public void onClick() { String downloadFileName = WikiStorageBulkServiceImpl.WIKIEXPORT_STORAGE.replace("\\", "/") + fileName; addClientUpdate(new FClientUpdate("document.location='" + EndpointImpl.api().getRequestMapper().getContextPath() + "/file/?file=" + downloadFileName + "&type=zip&root=" + AbstractFileServlet.RootDirectory.IWB_WORKING_DIR + "'")); } }; } /** * UI component for wiki page selection. * @author as */ protected static class WikiPageSelectionTable extends FSelectableTable<WikiPageMeta> { private final WikiStorageBulkServiceImpl wsApi; private final Config config; public WikiPageSelectionTable(String id, Config config, WikiStorageBulkServiceImpl wsApi) { super(id); this.config = config; this.wsApi = wsApi; initializeControlComponents(); updateTableModelInternal(new WikiStorageBulkServiceImpl.VoidFilter()); } private void initializeControlComponents() { /* FILTER BUTTONS */ if (isFilterVisible(FilterDescription.ALL)) addControlComponent(createFilterNoneButton(wsApi, this), "floatLeft"); if (isFilterVisible(FilterDescription.BOOTSTRAP)) addControlComponent(createFilterBootstrapButton(wsApi, this), "floatLeft"); if (isFilterVisible(FilterDescription.USERPAGES)) addControlComponent(createFilterUserpagesButton(wsApi, this), "floatLeft"); if (isFilterVisible(FilterDescription.IMPORTED)) addControlComponent(createFilterImportedButton(wsApi, this), "floatLeft"); if (isFilterVisible(FilterDescription.USERPAGES_SHADOWING_BOOTSTRAP)) addControlComponent(createFilterUserPagesShadowingBootstrapButton(wsApi, this), "floatLeft"); if (isFilterVisible(FilterDescription.NAMESPACE)) addControlComponent(createFilterNamespacePrefixButton(wsApi, this), "floatLeft"); addControlComponent(new FHTML("filterSeparator", " "), "clear"); /* SELECT ALL / UNSELECT ALL BUTTONS */ addControlComponent(createSelectAllButton(), "floatLeft"); addControlComponent(createUnselectAllButton(), "floatLeft"); addControlComponent(createSelectPresetButton(wsApi, this), "floatLeft"); addControlComponent(new FHTML("selectorSeparator", " "), "clear"); /* DELETE / RESTORE BUTTON */ if (isControlVisible(Control.DELETEREVISIONS)) addControlComponent(createDeleteRevisionsButton(wsApi, this), "floatLeft"); if (isControlVisible(Control.RESTORELATESTREVISION)) addControlComponent(createRestoreLatestRevisionButton(wsApi, this, getDefaultFilter(config)), "floatLeft"); addControlComponent(new FHTML("controlSeparator", " "), "clear"); /* CREATE EXPORT BUTTON */ if (isControlVisible(Control.EXPORT)) addControlComponent(createExportButton(wsApi, this), "floatLeft"); addControlComponent(new FHTML("endFloatLeft", " "), "clear"); } protected boolean isFilterVisible(FilterDescription filterDescription) { // check widget config: if no filter is defined, we show all filters return (config == null) || (config.visibleFilters == null) || config.visibleFilters.contains(filterDescription); } protected boolean isControlVisible(Control control) { // check widget config: if no config is defined, we show all controls return (config == null) || (config.visibleControls == null) || config.visibleControls.contains(control); } @SuppressWarnings("unchecked") public FSelectableTableModel<WikiPageMeta> getModelSafe() { return (FSelectableTableModel<WikiPageMeta>) getModel(); } /** * Update the table model using the provided filter and refresh. The * table is sorted according to column "wikiPage" * * @param filter */ public void updateTableModel(Filter filter) { updateTableModelInternal(filter); populateView(); } private void updateTableModelInternal(Filter filter) { // table model has columns {WikiPage, Comment, Type, Number of Revisions} FSelectableTableModel<WikiPageMeta> tm = new FSelectableTableModelImpl<WikiPageMeta>( Arrays.asList("Wiki page", "Comment", "Type", "Number of revisions"), true); for (WikiPageMeta w : wsApi.getAllWikipages(filter)) { if (w.getRevision() == null) throw new IllegalStateException( "Wiki store invalid: no revision found for " + w.getPageUri().stringValue()); tm.addRow(new Object[] { new FValue("v" + Rand.getIncrementalFluidUUID(), w.getPageUri()), new HtmlString(w.getRevision().comment), getTypeString(w), w.getNumberOfRevisions() }, w); } setModel(tm); setSortColumn(1, FTable.SORT_ASCENDING); } private String getTypeString(WikiPageMeta w) { if (w.isBootstrap()) { return "Bootstrap " + w.getRevision().bootstrapVersion(); } else { SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return "User page (" + sf.format(w.getRevision().date) + ")"; } } } /** * the button trigger a popup window that offers two options: either delete old revisions * and keep a number of the latest or delete a number of the latest and keep the old revisions * additionally there is a checkbox for keeping the bootstrap revisions * @param table * @return */ protected static FButton createDeleteRevisionsButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable table) { FButton deleteRevisionsButton = new FButton(Rand.getIncrementalFluidUUID(), "Delete revisions") { @Override public void onClick() { final List<WikiPageMeta> pages = table.getSelectedObjects(); if (pages.size() == 0) { getPage().getPopupWindowInstance().showInfo("Select pages to proceed"); return; } FPopupWindow pop = getPage().getPopupWindowInstance(); pop.removeAll(); pop.setTitle("Delete revisions of the selected pages"); ToolTip title = new ToolTip("title", "Select operation", "You can select one of the two operations, " + "enter a number of revisions and decide if the bootstrap revisions should be deleted or not"); title.appendClazz("formtp"); // First option : delete all but a specific number of latest revisions final FTextInput2 numberInput = createNumberInput(); FContainer option1 = new FContainer("option1"); option1.add(createLabel("Keep the latest")); option1.add(numberInput); option1.add(createLabel("revision(s) of the selected pages and delete others")); // Second Option: delete a number of latest revisions final FTextInput2 numberInput2 = createNumberInput(); FContainer option2 = new FContainer("option2"); option2.add(createLabel("Delete the latest")); option2.add(numberInput2); option2.add(createLabel("revision(s) of the selected pages")); // Both options together as radio buttons final FRadioButtonGroup radioButtons = new FRadioButtonGroup("radio"); radioButtons.addRadioButton("", "1", option1); radioButtons.addRadioButton("", "2", option2); radioButtons.appendClazz("radioButtons"); //Checkbox to define if the bootstrap revisions should be deleted or not final FCheckBox keepBootstrap = new FCheckBox("keepBootstrap"); keepBootstrap.setLabel("Keep the bootstrap revisions"); keepBootstrap.setChecked(true); keepBootstrap.addStyle("margin-top", "20px"); keepBootstrap.addStyle("margin-bottom", "20px"); //Execute button FButton deleteRevisionsNumberButton = new FButton("deleteRevisionsButton", "Execute") { @Override public void onClick() { if (radioButtons.checked == null) { getPage().getPopupWindowInstance().showInfo("Select a delete operation"); return; } String option = radioButtons.checked.getValue(); Integer number = 0; try { number = Integer.parseInt(option.equals("1") ? numberInput.getValue() : option.equals("2") ? numberInput2.getValue() : null); } catch (NumberFormatException e) { getPage().getPopupWindowInstance().showInfo("Enter a number of revisions to keep"); return; } for (WikiPageMeta wpm : pages) { if (option.equals("1")) wsApi.deleteRevisions(wpm.getPageUri(), number, keepBootstrap.checked); else if (option.equals("2")) wsApi.deleteLatestRevisions(wpm.getPageUri(), number, keepBootstrap.checked); } final FPopupWindow pop = getPage().getPopupWindowInstance(); pop.showOkDialogWithAction("Info", "Selected revisions are successfully deleted", new FEventListener() { @Override public void handleClientSideEvent(FEvent evt) { pop.doCallback("location.reload(true);"); } }); } }; deleteRevisionsNumberButton.setConfirmationQuestion( "Old revisions of the selected pages will be permanently deleted. Do you really want to proceed?"); deleteRevisionsNumberButton.addStyle("width", "70px"); deleteRevisionsNumberButton.addStyle("margin", "auto"); //Add all components to the popup window pop.add(title); pop.add(radioButtons); pop.add(keepBootstrap); pop.add(deleteRevisionsNumberButton); pop.populateAndShow(); } private FTextInput2 createNumberInput() { FTextInput2 numberInput = new FTextInput2("number" + Rand.getIncrementalFluidUUID()); numberInput.addStyle("float", "left"); numberInput.addStyle("margin-left", "10px"); numberInput.addStyle("margin-right", "10px"); numberInput.setSize(3); numberInput.setValueWithoutRefresh("1"); return numberInput; } private FLabel createLabel(String text) { FLabel label = new FLabel("label" + Rand.getIncrementalFluidUUID(), text); label.addStyle("margin-top", "5px"); label.addStyle("float", "left"); return label; } }; return deleteRevisionsButton; } protected static FButton createRestoreLatestRevisionButton(final WikiStorageBulkServiceImpl wsApi, final WikiPageSelectionTable wpTable, final Filter defaultFilter) { return new FButton("restore", "Restore latest bootstrap") { @Override public void onClick() { List<Exception> errors = new ArrayList<Exception>(); int count = 0; List<WikiPageMeta> selectedObjects = wpTable.getSelectedObjects(); if (selectedObjects.size() == 0) { getPage().getPopupWindowInstance() .showInfo("Please select at least one wiki page to be restored."); return; } for (WikiPageMeta w : selectedObjects) { try { boolean res = wsApi.restoreLatestBootstrap(w.getPageUri()); if (res) count++; } catch (Exception e) { errors.add(e); } } wpTable.updateTableModel(defaultFilter); FPopupWindow p = getPage().getPopupWindowInstance(); p.removeAll(); p.setTitle("Info"); StringBuilder msg = new StringBuilder(); if (errors.isEmpty() && count == selectedObjects.size()) { msg.append("<p><b>Selected bootstrap pages restored successfully.</b></p>"); } else if (errors.size() > 0 && count > 0) { msg.append("<p><b>Selected bootstrap pages partially restored.</b></p><p>Problems:</p>"); msg.append("<ul>"); for (Exception e : errors) msg.append("<li>").append(e.getMessage()).append("</li>"); msg.append("</li><br/>"); } else if (count < selectedObjects.size()) { msg.append("<p><b>Selected bootstrap pages partially restored: " + count + " of " + selectedObjects.size() + "</b></p>"); msg.append("<p>This may happen for one of the following reasons:</p>"); msg.append("<ol><li>A selected page was already a bootstrap page</li>"); msg.append("<li>A selected page does not have a bootstrap version</li>"); msg.append( "<li>The content of the current page and the last bootstrap are identical</li></ol><br/>"); } p.add(new FHTML(Rand.getIncrementalFluidUUID(), msg.toString())); p.addCloseButton("Close"); p.populateAndShow(); } }; } /** * Get filter by filterDescription. * @param filter filterDescription * @return filter instance or <code>null</code>, if not available */ private static Filter getFilterFor(FilterDescription filterDescription) { if (filterDescription == null) return null; switch (filterDescription) { case ALL: return new VoidFilter(); case USERPAGES: return new NotFromBootstrapFilter(); case BOOTSTRAP: return new FromBootstrapFilter(); case IMPORTED: return new ImportedFromWikibotFilter(); case USERPAGES_SHADOWING_BOOTSTRAP: return new UserPageShadowsBootstrapFilter(); case NAMESPACE: // this filter cannot be pre-applied, as it needs user-input return null; case TEMPLATE: return new TemplateFilter(); default: break; } ; return null; } /** * Method for identifying the pre-defined filter from {@link Config#filter}, * if a pre-defined filter has been specified. We need this method for being * able to access this information form any part of the code. Otherwise it * is possible that the widget reloads applying a {@link VoidFilter}, thus * displaying wiki pages it is not supposed to. * * @param config * Config holding the pre-defined filter * @return The filter specified in <code>config</code> or the * {@link VoidFilter} if none has been specified or it cannot be * resolved. */ private static Filter getDefaultFilter(Config config) { if ((config != null) && (config.filter != null)) { Filter filter = getFilterFor(config.filter); if (filter != null) return filter; } return new VoidFilter(); } }