/* * Copyright (C) 2011 Jan Pokorsky * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cz.cas.lib.proarc.webapp.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.i18n.client.Dictionary; import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceController; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Window; import com.smartgwt.client.core.Function; import com.smartgwt.client.data.Criteria; import com.smartgwt.client.data.DSCallback; import com.smartgwt.client.data.DSRequest; import com.smartgwt.client.data.DSResponse; import com.smartgwt.client.data.Record; import com.smartgwt.client.types.Autofit; import com.smartgwt.client.types.ClickMaskMode; import com.smartgwt.client.types.SelectionStyle; import com.smartgwt.client.util.I18nUtil; import com.smartgwt.client.util.Page; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.widgets.IconButton; import com.smartgwt.client.widgets.Label; import com.smartgwt.client.widgets.layout.HLayout; import com.smartgwt.client.widgets.layout.Layout; import com.smartgwt.client.widgets.layout.VLayout; import com.smartgwt.client.widgets.menu.IconMenuButton; import com.smartgwt.client.widgets.menu.Menu; import com.smartgwt.client.widgets.menu.MenuItem; import com.smartgwt.client.widgets.menu.events.ItemClickEvent; import com.smartgwt.client.widgets.menu.events.ItemClickHandler; import com.smartgwt.client.widgets.toolbar.ToolStrip; import com.smartgwt.client.widgets.tree.Tree; import com.smartgwt.client.widgets.tree.TreeGrid; import com.smartgwt.client.widgets.tree.TreeNode; import com.smartgwt.client.widgets.tree.events.FolderClosedEvent; import com.smartgwt.client.widgets.tree.events.FolderClosedHandler; import com.smartgwt.client.widgets.tree.events.LeafClickEvent; import com.smartgwt.client.widgets.tree.events.LeafClickHandler; import cz.cas.lib.proarc.webapp.client.ClientUtils.SweepTask; import cz.cas.lib.proarc.webapp.client.action.AbstractAction; import cz.cas.lib.proarc.webapp.client.action.ActionEvent; import cz.cas.lib.proarc.webapp.client.action.Actions; import cz.cas.lib.proarc.webapp.client.ds.LanguagesDataSource; import cz.cas.lib.proarc.webapp.client.ds.LocalizationDataSource; import cz.cas.lib.proarc.webapp.client.ds.RestConfig; import cz.cas.lib.proarc.webapp.client.ds.UserDataSource; import cz.cas.lib.proarc.webapp.client.ds.UserPermissionDataSource; import cz.cas.lib.proarc.webapp.client.ds.ValueMapDataSource; import cz.cas.lib.proarc.webapp.client.presenter.DeviceManager; import cz.cas.lib.proarc.webapp.client.presenter.DeviceManaging.DeviceManagerPlace; import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectCreating.DigitalObjectCreatorPlace; import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectCreator; import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectEditor; import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectManager; import cz.cas.lib.proarc.webapp.client.presenter.DigitalObjectManaging.DigitalObjectManagerPlace; import cz.cas.lib.proarc.webapp.client.presenter.ImportPresenter; import cz.cas.lib.proarc.webapp.client.presenter.Importing.ImportPlace; import cz.cas.lib.proarc.webapp.client.presenter.Importing.ImportPlace.Type; import cz.cas.lib.proarc.webapp.client.presenter.UserManaging.UsersPlace; import cz.cas.lib.proarc.webapp.client.presenter.WorkflowJobsEditor; import cz.cas.lib.proarc.webapp.client.presenter.WorkflowManaging.WorkflowJobPlace; import cz.cas.lib.proarc.webapp.client.presenter.WorkflowManaging.WorkflowNewJobPlace; import cz.cas.lib.proarc.webapp.client.presenter.WorkflowManaging.WorkflowTaskPlace; import cz.cas.lib.proarc.webapp.client.presenter.WorkflowNewJobEditor; import cz.cas.lib.proarc.webapp.client.presenter.WorkflowTasksEditor; import cz.cas.lib.proarc.webapp.client.widget.AboutWindow; import cz.cas.lib.proarc.webapp.client.widget.LoginWindow; import cz.cas.lib.proarc.webapp.client.widget.UserInfoView; import cz.cas.lib.proarc.webapp.client.widget.UsersView; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; public class Editor implements EntryPoint { public static final String CSS_PANEL_DESCRIPTION_TITLE = "proarcPanelDescriptionTitle"; public static final String CSS_HEADER_INSIDE_FORM = "proarcHeaderInsideForm"; private static final Logger LOG = Logger.getLogger(Editor.class.getName()); /** {@link TreeNode } attribute to associate the node with {@link Place}. */ private static final String PLACE_ATTRIBUTE = "GoToPlace"; private static final String LOCALE_ATTRIBUTE = "locale"; private static final String GLOBAL_MENU_CONTAINER_ID = "proarcGlobalMenuContainer"; private static Editor INSTANCE; private ClientMessages i18n; private PresenterFactory presenterFactory; private final HashSet<String> permissions = new HashSet<String>(); private EditorWorkFlow editorWorkFlow; private Layout editorDisplay; private SweepTask sweepTask; private ErrorHandler errorHandler; private Record user; public static Editor getInstance() { return INSTANCE; } public EditorWorkFlow getEditorWorkFlow() { return editorWorkFlow; } public PresenterFactory getPresenterFactory() { return presenterFactory; } public ErrorHandler getTransportErrorHandler() { return errorHandler; } public Record getUser() { return user; } @Override public void onModuleLoad() { INSTANCE = this; initLogging(); ClientUtils.info(LOG, "onModuleLoad:\n module page: %s\n host page: %s" + "\n getModuleName: %s\n getPermutationStrongName: %s\n version: %s" + "\n Page.getAppDir: %s, \n Locale: %s", GWT.getModuleBaseURL(), GWT.getHostPageBaseURL(), GWT.getModuleName(), GWT.getPermutationStrongName(), GWT.getVersion(), Page.getAppDir(), LanguagesDataSource.activeLocale() ); // remove the loading wrapper. See index.html DOM.getElementById("loadingWrapper").removeFromParent(); I18nUtil.initMessages(ClientUtils.createSmartGwtMessages()); i18n = GWT.create(ClientMessages.class); errorHandler = new ErrorHandler(); errorHandler.initTransportErrorHandler(); LoginWindow.login(); presenterFactory = new PresenterFactory(i18n); editorWorkFlow = new EditorWorkFlow(getDisplay(), presenterFactory, i18n); presenterFactory.setPlaceController(editorWorkFlow.getPlaceController()); final TreeGrid menu = createMenu(); sweepTask = new SweepTask() { @Override public void processing() { SC.clearPrompt(); TreeNode[] menuContent = createMenuContent(); Tree tree = menu.getTree(); TreeNode root = tree.getRoot(); tree.addList(menuContent, root); tree.openAll(); editorWorkFlow.init(); } }; final HLayout mainLayout = new HLayout(); // mainLayout.setLayoutMargin(5); mainLayout.setWidth100(); mainLayout.setHeight100(); mainLayout.setMembers(getDisplay()); Canvas mainHeader = createMainHeader(menu); VLayout desktop = new VLayout(0); desktop.setWidth100(); desktop.setHeight100(); desktop.setMembers(mainHeader, mainLayout); desktop.draw(); SC.showPrompt(i18n.Editor_InitialLoading_Msg()); LocalizationDataSource.getInstance().initOnStart(sweepTask.expect()); ValueMapDataSource.getInstance().initOnStart(sweepTask.expect()); loadPermissions(); } private void loadPermissions() { sweepTask.expect(); UserPermissionDataSource.getInstance().fetchData(null, new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { if (RestConfig.isStatusOk(response)) { Record[] data = response.getData(); permissions.clear(); permissions.addAll(UserPermissionDataSource.asPermissions(data)); sweepTask.release(); } } }); } /** * Gets container to display editor activities. */ private Layout getDisplay() { if (editorDisplay == null) { editorDisplay = new HLayout(); editorDisplay.setHeight100(); editorDisplay.setWidth100(); } return editorDisplay; } private Canvas createMainHeader(TreeGrid menu) { ToolStrip mainHeader = new ToolStrip(); mainHeader.setWidth100(); mainHeader.setHeight(40); mainHeader.addSpacer(6); Label headerItem = new Label(); headerItem.setWrap(false); headerItem.setIcon("16/logo.png"); headerItem.setIconHeight(16); headerItem.setIconWidth(205); // headerItem.setIcon("24/logo.png"); // headerItem.setIconHeight(24); // headerItem.setIconWidth(307); // headerItem.setIcon("20/logo.png"); // headerItem.setIconHeight(20); // headerItem.setIconWidth(256); mainHeader.addMember(createGlobalMenuButton(menu)); mainHeader.addMember(createLangMenu()); createUserLink(mainHeader, mainHeader.getMembers().length); mainHeader.addFill(); mainHeader.addMember(headerItem); mainHeader.addSpacer(6); return mainHeader; } private Canvas createLangMenu() { String activeLocale = LanguagesDataSource.activeLocale(); IconMenuButton langMenuButton = Actions.asIconMenuButton(Actions.emptyAction(activeLocale, null, null), this); langMenuButton.setCanFocus(Boolean.FALSE); Menu m = new Menu(); m.setShowShadow(Boolean.TRUE); m.addItem(createLangItem("cs", "Česky", activeLocale)); m.addItem(createLangItem("en", "English", activeLocale)); langMenuButton.setMenu(m); m.addItemClickHandler(new ItemClickHandler() { @Override public void onItemClick(ItemClickEvent event) { MenuItem item = event.getItem(); if (!Boolean.TRUE.equals(item.getChecked())) { switchLocale(item.getAttribute(LOCALE_ATTRIBUTE)); } } }); Record rec = m.getDataAsRecordList().find(LOCALE_ATTRIBUTE, activeLocale); langMenuButton.setTitle(rec.getAttribute("title")); return langMenuButton; } private MenuItem createLangItem(String locale, String title, String activeLocale) { MenuItem mi = new MenuItem(title); mi.setAttribute(LOCALE_ATTRIBUTE, locale); if (locale.equals(activeLocale)) { mi.setChecked(Boolean.TRUE); } return mi; } /** * Switches the application locale. * @param locale new locale */ private void switchLocale(String locale) { UrlBuilder urlBuilder = Window.Location.createUrlBuilder(); urlBuilder.setParameter(LOCALE_ATTRIBUTE, locale); String url = urlBuilder.buildString(); Window.Location.assign(url); } private Canvas createUserLink(final ToolStrip mainHeader, final int index) { final UserInfoView userInfoView = new UserInfoView(i18n); final IconButton userButtonRef[] = new IconButton[1]; final IconButton userButton = Actions.asIconButton(new AbstractAction(null, null, null) { @Override public void performAction(ActionEvent event) { userInfoView.show(userButtonRef[0], "bottom", true); } }, this); userButtonRef[0] = userButton; userButton.setCanFocus(Boolean.FALSE); UserDataSource.getInstance().fetchData(new Criteria(UserDataSource.FIELD_WHOAMI, "true"), new DSCallback() { @Override public void execute(DSResponse response, Object rawData, DSRequest request) { String title = "Unknown user"; if (response.getStatus() == DSResponse.STATUS_SUCCESS) { Record[] data = response.getData(); if (data.length > 0) { user = data[0]; title = user.getAttribute(UserDataSource.FIELD_USERNAME); } } userButton.setTitle(title); mainHeader.addMember(userButton, index); } }); return userButton; } private Canvas createGlobalMenuButton(final TreeGrid menu) { // Render the menu as HLayout instead of Window to get rid of edges introduced by SmartGWT 4.0 final HLayout menuWindow = new HLayout(); menuWindow.setID(GLOBAL_MENU_CONTAINER_ID); menuWindow.setShowShadow(true); menuWindow.setWidth(200); menuWindow.setMembers(menu); final IconMenuButton[] globalMenuButton = new IconMenuButton[1]; globalMenuButton[0] = Actions.asIconMenuButton(new AbstractAction( i18n.MainMenu_Title(), null, null) { @Override public void performAction(ActionEvent event) { // canOcclude not fully supported in SmartGWT 4.0; introduce by patch 4.0-p; do not use yet // menuWindow.showNextTo(globalMenuButton[0], "bottom", true); menuWindow.showNextTo(globalMenuButton[0], "bottom"); menuWindow.showClickMask(new Function() { @Override public void execute() { menuWindow.hide(); } }, ClickMaskMode.SOFT, new Canvas[] {menuWindow}); menu.focus(); } }, new Object()); globalMenuButton[0].setAutoWidth(); globalMenuButton[0].setCanFocus(Boolean.FALSE); return globalMenuButton[0]; } private TreeNode[] createMenuContent() { TreeNode[] trees = new TreeNode[] { createTreeNode("Import", i18n.MainMenu_Import_Title(), createTreeNode("New Batch", i18n.MainMenu_Import_NewBatch_Title(), new ImportPlace(Type.CONTENT)), createTreeNode("History", i18n.MainMenu_Import_Edit_Title(), new ImportPlace(Type.HISTORY))), createTreeNode("Edit", i18n.MainMenu_Edit_Title(), createTreeNode("New Object", i18n.MainMenu_Edit_NewObject_Title(), new DigitalObjectCreatorPlace()), createTreeNode("Search", i18n.MainMenu_Edit_Edit_Title(), new DigitalObjectManagerPlace()) ), createTreeNode("RDFlow", i18n.MainMenu_Workflow_Title(), createTreeNode("New Job", i18n.MainMenu_Workflow_NewJob_Title(), new WorkflowNewJobPlace()), createTreeNode("Jobs", i18n.MainMenu_Workflow_Jobs_Title(), new WorkflowJobPlace()), createTreeNode("Tasks", i18n.MainMenu_Workflow_Tasks_Title(), new WorkflowTaskPlace()) ), createTreeNode("Devices", i18n.MainMenu_Devices_Title(), new DeviceManagerPlace()), // createTreeNode("Statistics", i18n.MainMenu_Statistics_Title()), createProtectedTreeNode("Users", i18n.MainMenu_Users_Title(), new UsersPlace(), Arrays.asList("proarc.permission.admin")), createProtectedTreeNode("Console", i18n.MainMenu_Console_Title(), Arrays.asList("proarc.permission.admin")), createTreeNode("About", i18n.AboutWindow_Title()), }; trees = reduce(trees); for (int i = 0; i < trees.length; i++) { TreeNode treeNode = trees[i]; ClientUtils.fine(LOG, "TreeNode.array: %s, i: %s", treeNode.getName(), i); } return trees; } private TreeGrid createMenu() { final TreeGrid menu = new TreeGrid(); menu.setAutoFitData(Autofit.VERTICAL); menu.setLeaveScrollbarGap(false); menu.setShowHeader(false); menu.setShowOpener(false); menu.setShowOpenIcons(false); menu.setCanCollapseGroup(false); menu.setSelectionType(SelectionStyle.NONE); menu.addFolderClosedHandler(new FolderClosedHandler() { @Override public void onFolderClosed(FolderClosedEvent event) { event.cancel(); } }); menu.addLeafClickHandler(new LeafClickHandler() { @Override public void onLeafClick(LeafClickEvent event) { for (Canvas parent = menu.getParentCanvas(); parent != null; parent = parent.getParentCanvas()) { if (GLOBAL_MENU_CONTAINER_ID.equals(parent.getID())) { parent.hide(); break; } } ClientUtils.fine(LOG, "menu.getSelectedPaths: %s\nmenu.getSelectedRecord: %s", menu.getSelectedPaths(), menu.getSelectedRecord()); String name = event.getLeaf().getName(); final PlaceController placeController = getEditorWorkFlow().getPlaceController(); Object placeObj = event.getLeaf().getAttributeAsObject(PLACE_ATTRIBUTE); if (placeObj instanceof Place) { placeController.goTo((Place) placeObj); return ; } if ("Console".equals(name)) { SC.showConsole(); } else if ("About".equals(name)) { AboutWindow.getInstance(i18n).show(); } else { Layout placesContainer = getDisplay(); placesContainer.removeMembers(placesContainer.getMembers()); } } }); return menu; } public boolean hasPermission(String permission) { return permissions.contains(permission); } private boolean checkCredentials(List<String> requires) { if (requires != null && !permissions.containsAll(requires)) { return true; } return false; } private TreeNode createProtectedTreeNode(String name, String displayName, Place place, List<String> requires) { if (checkCredentials(requires)) { return null; } return createTreeNode(name, displayName, place, (TreeNode[]) null); } private TreeNode createProtectedTreeNode(String name, String displayName, List<String> requires, TreeNode... children) { if (checkCredentials(requires)) { return null; } return createTreeNode(name, displayName, null, children); } private TreeNode createTreeNode(String name, String displayName, TreeNode... children) { return createTreeNode(name, displayName, null, children); } private TreeNode createTreeNode(String name, String displayName, Place place, TreeNode... children) { if (name == null) { throw new NullPointerException("name"); } TreeNode treeNode = new TreeNode(name); if (displayName != null) { treeNode.setTitle(displayName); } if (children != null && children.length > 0) { treeNode.setChildren(children); } if (place != null) { treeNode.setAttribute(PLACE_ATTRIBUTE, place); } return treeNode; } private static TreeNode[] reduce(TreeNode[] nodes) { ArrayList<TreeNode> result = new ArrayList<TreeNode>(nodes.length); for (TreeNode treeNode : nodes) { if (treeNode != null) { result.add(treeNode); } } return result.toArray(new TreeNode[result.size()]); } private void initLogging() { Dictionary levels = Dictionary.getDictionary("EditorLoggingConfiguration"); for (String loggerName : levels.keySet()) { String levelValue = levels.get(loggerName); try { Level level = Level.parse(levelValue); Logger logger = Logger.getLogger(loggerName); logger.setLevel(level); Logger.getLogger("").info(ClientUtils.format( "logger: '%s', levelValue: %s", loggerName, level)); } catch (IllegalArgumentException ex) { Logger.getLogger("").log(Level.SEVERE, ClientUtils.format("logger: '%s', levelValue: %s", loggerName, levelValue), ex); } } if (GWT.isProdMode()) { // XXX SmartGWT 3.0 ignores thrown exceptions in production mode. // Javascript stack traces are useless but messages can be valuable GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() { @Override public void onUncaughtException(Throwable e) { StringBuilder sb = new StringBuilder(); for (Throwable t = e; t != null; t = t.getCause()) { sb.append("* ").append(t.getClass().getName()).append(": ") .append(t.getLocalizedMessage()).append("\n"); for (StackTraceElement elm : t.getStackTrace()) { sb.append(" ").append(elm.toString()).append("\n"); } } // visible in javascript console; Window.alert is too much intrusive. LOG.log(Level.SEVERE, e.getMessage(), e); // Window.alert(sb.toString()); } }); } } public static final class PresenterFactory { private ImportPresenter importPresenter; private DeviceManager deviceManager; private DigitalObjectCreator digitalObjectCreator; private DigitalObjectEditor digitalObjectEditor; private DigitalObjectManager digitalObjectManager; private UsersView users; private WorkflowJobsEditor workflowJobs; private WorkflowTasksEditor workflowTasks; private final ClientMessages i18n; private PlaceController placeController; private WorkflowNewJobEditor workflowNew; PresenterFactory(ClientMessages i18n) { this.i18n = i18n; } void setPlaceController(PlaceController placeController) { this.placeController = placeController; } public ImportPresenter getImportPresenter() { if (importPresenter == null) { importPresenter = new ImportPresenter(i18n, placeController); } return importPresenter; } public DeviceManager getDeviceManager() { if (deviceManager == null) { deviceManager = new DeviceManager(i18n, placeController); } return deviceManager; } public DigitalObjectCreator getDigitalObjectCreator() { if (digitalObjectCreator == null) { digitalObjectCreator = new DigitalObjectCreator(i18n, placeController); } return digitalObjectCreator; } public DigitalObjectEditor getDigitalObjectEditor() { if (digitalObjectEditor == null) { digitalObjectEditor = new DigitalObjectEditor(i18n, placeController); } return digitalObjectEditor; } public DigitalObjectManager getDigitalObjectManager() { if (digitalObjectManager == null) { digitalObjectManager = new DigitalObjectManager(i18n, placeController); } return digitalObjectManager; } public UsersView getUsers() { if (users == null) { users = new UsersView(i18n); } return users; } public WorkflowJobsEditor getWorkflowJobs() { if (workflowJobs == null) { workflowJobs = new WorkflowJobsEditor(i18n, placeController); } return workflowJobs; } public WorkflowNewJobEditor getWorkflowNewJob() { if (workflowNew == null) { workflowNew = new WorkflowNewJobEditor(i18n, placeController); } return workflowNew; } public WorkflowTasksEditor getWorkflowTasks() { if (workflowTasks == null) { workflowTasks = new WorkflowTasksEditor(i18n, placeController); } return workflowTasks; } } }