Greg Sheremeta has uploaded a new change for review.

Change subject: userportal, webadmin: changed csh to use per-locale mapping
......................................................................

userportal, webadmin: changed csh to use per-locale mapping

Changed context-sensitive help infrastructure to use per-locale
mapping files. Previously there was one mapping shared by all
locales, but that no longer fits the documentation release
process.

Change-Id: I1f854a9d7034d436730a4e82c88963a854e2c2b6
Bug-Url: https://bugzilla.redhat.com/1204434
Signed-off-by: Greg Sheremeta <gsher...@redhat.com>
---
A 
backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/ContextSensitiveHelpMappingServlet.java
D 
backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/HelpTagJsonServlet.java
M backend/manager/modules/docs/src/main/webapp/WEB-INF/web.xml
M 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/presenter/AbstractModelBoundPopupPresenterWidget.java
A 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/ContextSensitiveHelpManager.java
D 
frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/DocumentationPathTranslator.java
M 
frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Configurator.java
M 
frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/uicommon/UserPortalConfigurator.java
M 
frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/WebAdminConfigurator.java
9 files changed, 447 insertions(+), 264 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/97/39297/1

diff --git 
a/backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/ContextSensitiveHelpMappingServlet.java
 
b/backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/ContextSensitiveHelpMappingServlet.java
new file mode 100644
index 0000000..f1a717b
--- /dev/null
+++ 
b/backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/ContextSensitiveHelpMappingServlet.java
@@ -0,0 +1,251 @@
+package org.ovirt.engine.docs.utils.servlet;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.node.ObjectNode;
+import org.ovirt.engine.core.utils.EngineLocalConfig;
+import org.ovirt.engine.core.utils.servlet.ServletUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This servlet serves the context-sensitve help mapping files (JSON format) 
the the web UI.
+ *
+ * Each application has documentation in multiple locales, and each locale may 
have multiple mapping files,
+ * perhaps grouped by section in the application, etc.
+ *
+ * The servlet handles loading and compressing all of that information into 
one JSON file per application.
+ *
+ * Roughly:
+ *   * Get the directory where the context-sensitive help package is installed
+ *   * Detect which locales are installed
+ *   * For each locale
+ *     * get the conf.d directory
+ *     * for each application subdir (webadmin, userportal, etc)
+ *       * Look for json mapping files
+ *       * concatenate them into a single json file for this locale+application
+ *   * concatenate all of the locale+application jsons into one giant json per 
application
+ *
+ * This happens for every request of an application's json mapping file, which 
is once per user login.
+ *
+ * @see ContextSensitiveHelpManager for the client-side portion of this 
operation.
+ */
+public class ContextSensitiveHelpMappingServlet extends HttpServlet {
+
+    private static final long serialVersionUID = -393894763659009626L;
+    private static final Logger log = 
LoggerFactory.getLogger(ContextSensitiveHelpMappingServlet.class);
+
+    private static final String MANUAL_DIR_KEY = "manualDir"; //$NON-NLS-1$
+    private static final String CSH_MAPPING_DIR = "csh.conf.d"; //$NON-NLS-1$
+    private static final String JSON = ".json"; //$NON-NLS-1$
+
+    // parse xxxxxx from /some/path/xxxxxx.json
+    private static Pattern REQUEST_PATTERN = 
Pattern.compile(".*?(?<key>[^/]*)\\.json"); //$NON-NLS-1$
+
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    /**
+     * Respond to a GET request for the CSH mapping file. See class Javadoc 
for the algorithm.
+     *
+     * @see 
javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, 
javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws ServletException, IOException {
+
+        String application = getApplication(request);
+        if (application == null) {
+            log.warn("ContextSensitiveHelpMappingServlet could not handle 
request. URL = " + request.getRequestURI()); //$NON-NLS-1$
+            return;
+        }
+
+        String manualPath = getManualDir(getServletConfig());
+        List<String> locales = getLocales(new File(manualPath));
+
+        ObjectNode appCsh = mapper.createObjectNode();
+
+        for (String locale : locales) {
+            File cshConfigDir = new File(manualPath + "/" + locale + "/" + 
CSH_MAPPING_DIR + "/" + application); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+            if (cshConfigDir.exists() && cshConfigDir.canRead()) {
+                List<JsonNode> configData = readJsonFiles(cshConfigDir);
+                // merge the data from all the files
+                if (configData.size() > 0) {
+                    JsonNode destination = configData.get(0);
+                    for (int i = 1; i < configData.size(); i++) {
+                        destination = merge(destination, configData.get(i));
+                    }
+
+                    appCsh.put(locale, destination);
+                }
+            }
+            else {
+                log.error("couldn't get csh directory: " + cshConfigDir); 
//$NON-NLS-1$
+            }
+        }
+
+        response.setContentType("application/json"); //$NON-NLS-1$
+        PrintStream printStream = new PrintStream(response.getOutputStream());
+        printStream.print(appCsh.toString());
+        printStream.flush();
+    }
+
+    /**
+     * parse xxxxxx from /some/path/xxxxxx.json
+     */
+    protected String getApplication(HttpServletRequest request) {
+        Matcher m = REQUEST_PATTERN.matcher(request.getRequestURI());
+        if (m.matches()) {
+            return m.group("key"); //$NON-NLS-1$
+        }
+        return null;
+    }
+
+    /**
+     * Read the json files from the directory.
+     *
+     * @param configPath directory to read
+     * @return List<JsonNode> containing each file read
+     */
+    protected List<JsonNode> readJsonFiles(File configPath) {
+        List<JsonNode> nodes = new ArrayList<JsonNode>();
+        List<String> jsonFiles = getJsonFiles(configPath);
+
+        for (String jsonFile : jsonFiles) {
+
+            File file = new File(configPath, jsonFile);
+            if (file.exists() && file.canRead()) {
+                BufferedReader reader = null;
+                try {
+                    reader = new BufferedReader(new 
FileReader(file.getAbsolutePath()));
+                    nodes.add(mapper.readTree(reader));
+                    log.info("Successfully read CSH mapping file '{}'", 
file.getAbsolutePath());
+                }
+                catch (IOException e) {
+                    log.error("Exception parsing documentation mapping file 
'{}': {}", //$NON-NLS-1$
+                            file.getAbsolutePath(), e.getMessage());
+                    log.error("Exception: ", e); //$NON-NLS-1$
+                }
+                finally {
+                    try {
+                        reader.close();
+                    } catch (IOException e) {
+                        log.error("Couldn't close CSH mapping file", e); 
//$NON-NLS-1$
+                    }
+                }
+            }
+        }
+        return nodes;
+    }
+
+    /**
+     * Return sorted list of names of the json config files in the config dir.
+     *
+     * @param configDir directory to search
+     * @return sorted list of file names
+     */
+    protected List<String> getJsonFiles(File configDir) {
+
+        List<String> jsonFiles = new ArrayList<>();
+
+        if (!configDir.exists() || !configDir.canRead()) {
+            log.error("csh configDir doesn't exist: " + configDir); 
//$NON-NLS-1$
+            return jsonFiles;
+        }
+        for (File configFile : configDir.listFiles()) {
+            if (configFile.isFile() && configFile.canRead() && 
configFile.getName().endsWith(JSON)) {
+                jsonFiles.add(configFile.getName());
+            }
+        }
+
+        // last file wins
+        Collections.sort(jsonFiles);
+        return jsonFiles;
+    }
+
+    /**
+     * Get List of the installed documentation locales.
+     *
+     * @param manualDir directory to search
+     * @return
+     */
+    protected List<String> getLocales(File manualDir) {
+
+        List<String> locales = new ArrayList<>();
+
+        if (!manualDir.exists() || !manualDir.canRead()) {
+            log.error("manual directory doesn't exist: " + manualDir); 
//$NON-NLS-1$
+            return locales;
+        }
+        for (File dir : manualDir.listFiles()) {
+            if (dir.isDirectory() && dir.canRead()) {
+                String name = dir.getName();
+                // fancy locale name detection
+                if (name.length() == 5 && name.indexOf("-") == 2) { 
//$NON-NLS-1$
+                    locales.add(name);
+                }
+            }
+        }
+
+        return locales;
+    }
+
+    /**
+     * Get the configured manual directory from the servlet config.
+     */
+    protected String getManualDir(ServletConfig config) {
+        EngineLocalConfig engineLocalConfig = EngineLocalConfig.getInstance();
+        String manualDir = 
ServletUtils.getAsAbsoluteContext(getServletContext().getContextPath(),
+                engineLocalConfig.expandString(
+                        
config.getInitParameter(MANUAL_DIR_KEY).replaceAll("%\\{", "\\${")) 
//$NON-NLS-1$ //$NON-NLS-2$
+        );
+        return manualDir;
+    }
+
+    /**
+     * Merge json objects. This is used to put json mappings from multiple 
config files into one object.
+     *
+     * Note that this method is recursive.
+     *
+     * @param destination destination json node
+     * @param source source json node.
+     * @return merged json object
+     */
+    protected static JsonNode merge(JsonNode destination, JsonNode source) {
+
+        Iterator<String> fieldNames = source.getFieldNames();
+        while (fieldNames.hasNext()) {
+
+            String fieldName = fieldNames.next();
+            JsonNode jsonNode = destination.get(fieldName);
+            // if field is an embedded object, recurse
+            if (jsonNode != null && jsonNode.isObject()) {
+                merge(jsonNode, source.get(fieldName));
+            }
+            // else it's a plain field
+            else if (destination instanceof ObjectNode) {
+                // overwrite field
+                JsonNode value = source.get(fieldName);
+                ((ObjectNode) destination).put(fieldName, value);
+            }
+        }
+
+        return destination;
+    }
+}
diff --git 
a/backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/HelpTagJsonServlet.java
 
b/backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/HelpTagJsonServlet.java
deleted file mode 100644
index db43bb0..0000000
--- 
a/backend/manager/modules/docs/src/main/java/org/ovirt/engine/docs/utils/servlet/HelpTagJsonServlet.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.ovirt.engine.docs.utils.servlet;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.codehaus.jackson.node.ObjectNode;
-import org.ovirt.engine.core.utils.EngineLocalConfig;
-import org.ovirt.engine.core.utils.servlet.ServletUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This servlet serves the help tag JSON files for easy consumption by GWT. It 
can merge
- * multiple files to limit server requests and handle overrides (last file 
wins).
- */
-public class HelpTagJsonServlet extends HttpServlet {
-    private static final long serialVersionUID = -3938947636590096259L;
-    private static final Logger log = 
LoggerFactory.getLogger(HelpTagJsonServlet.class);
-
-    private static final String CONFIG_FILE = "configFile"; //$NON-NLS-1$
-
-    private static ObjectMapper mapper = new ObjectMapper();
-
-    // parse xxxxxx from /some/path/xxxxxx.json
-    private static Pattern REQUEST_PATTERN = 
Pattern.compile(".*?(?<key>[^/]*)\\.json"); //$NON-NLS-1$
-    private static String REQUEST_PATTERN_KEY_GROUP = "key"; //$NON-NLS-1$
-    private static String HELPTAGS_PREFIX = "helptags."; //$NON-NLS-1$
-
-    private Map<String, String> cachedJson = new HashMap<String, String>();
-
-    @Override
-    public void init(ServletConfig config) throws ServletException {
-        super.init(config);
-
-        EngineLocalConfig engineLocalConfig = EngineLocalConfig.getInstance();
-        String configFilePath = ServletUtils.getAsAbsoluteContext(
-            getServletContext().getContextPath(),
-            engineLocalConfig.expandString(
-                    config.getInitParameter(CONFIG_FILE).replaceAll("%\\{", 
"\\${")) //$NON-NLS-1$ //$NON-NLS-2$
-        );
-
-        Set<Map.Entry<Object, Object>> properties = new 
HashSet<Map.Entry<Object, Object>>();
-        File configFile = new File(configFilePath);
-        if (configFile.exists() && configFile.canRead()) {
-            Properties p = new Properties();
-            try (FileInputStream fis = new FileInputStream(configFile)) {
-                p.load(fis);
-                properties = p.entrySet();
-            }
-            catch (IOException e) {
-                log.error("Problem parsing Properties file '{}': {}", 
//$NON-NLS-1$
-                        configFile.getAbsolutePath(),
-                        e.getMessage());
-                log.debug("Exception", e); //$NON-NLS-1$
-            }
-        }
-
-        // for every HELPTAGS_PREFIX line in the properties file, read all 
json files, merge them, and put them in cachedJson
-        for (Map.Entry<Object, Object> property : properties) {
-            String key = (String) property.getKey();
-            if (key.startsWith(HELPTAGS_PREFIX)) {
-                key = key.substring(HELPTAGS_PREFIX.length());
-                String[] jsonFiles = ((String) 
property.getValue()).trim().split("\\s*,\\s*"); //$NON-NLS-1$
-
-                // read the json files
-                List<JsonNode> nodes = new ArrayList<JsonNode>();
-                for (String jsonFile : jsonFiles) {
-
-                    // the json files are relative to the configFile's 
directory
-                    File file = new File(configFile.getParent(), jsonFile);
-                    if (file.exists() && file.canRead()) {
-                        try {
-                            BufferedReader reader = new BufferedReader(new 
FileReader(file.getAbsolutePath()));
-                            nodes.add(mapper.readTree(reader));
-                        }
-                        catch (IOException e) {
-                            log.error("Problem parsing documentation mapping 
file '{}': {}", //$NON-NLS-1$
-                                    file.getAbsolutePath(),
-                                    e.getMessage());
-                            log.debug("Exception", e); //$NON-NLS-1$
-                        }
-                    }
-                }
-
-                // merge the json files
-                if (nodes.size() > 0) {
-                    JsonNode destination = nodes.get(0);
-                    for (int i = 1; i < nodes.size(); i++) {
-                        destination = merge(destination, nodes.get(i));
-                    }
-
-                    this.cachedJson.put(key, destination.toString());
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws ServletException, IOException {
-        Matcher m = REQUEST_PATTERN.matcher(request.getRequestURI());
-        String content = "{}"; //$NON-NLS-1$
-        if (m.matches() && 
cachedJson.containsKey(m.group(REQUEST_PATTERN_KEY_GROUP))) {
-            content = cachedJson.get(m.group(REQUEST_PATTERN_KEY_GROUP));
-        }
-        response.setContentType("application/json"); //$NON-NLS-1$
-        PrintStream printStream = new PrintStream(response.getOutputStream());
-        printStream.print(content);
-        printStream.flush();
-    }
-
-    protected static JsonNode merge(JsonNode destination, JsonNode source) {
-
-        Iterator<String> fieldNames = source.getFieldNames();
-        while (fieldNames.hasNext()) {
-
-            String fieldName = fieldNames.next();
-            JsonNode jsonNode = destination.get(fieldName);
-            // if field is an embedded object, recurse
-            if (jsonNode != null && jsonNode.isObject()) {
-                merge(jsonNode, source.get(fieldName));
-            }
-            // else it's a plain field
-            else if (destination instanceof ObjectNode) {
-                // overwrite field
-                JsonNode value = source.get(fieldName);
-                ((ObjectNode) destination).put(fieldName, value);
-            }
-        }
-
-        return destination;
-    }
-}
diff --git a/backend/manager/modules/docs/src/main/webapp/WEB-INF/web.xml 
b/backend/manager/modules/docs/src/main/webapp/WEB-INF/web.xml
index 7dca9bb..c9164021 100644
--- a/backend/manager/modules/docs/src/main/webapp/WEB-INF/web.xml
+++ b/backend/manager/modules/docs/src/main/webapp/WEB-INF/web.xml
@@ -75,18 +75,18 @@
   </servlet-mapping>
 
 
-  <!-- help tag mapping servlet -->
+  <!-- ContextSensitiveHelpMappingServlet -->
   <servlet>
-    <servlet-name>HelpTagJsonServlet</servlet-name>
-    
<servlet-class>org.ovirt.engine.docs.utils.servlet.HelpTagJsonServlet</servlet-class>
+    <servlet-name>ContextSensitiveHelpMappingServlet</servlet-name>
+    
<servlet-class>org.ovirt.engine.docs.utils.servlet.ContextSensitiveHelpMappingServlet</servlet-class>
     <init-param>
-      <param-name>configFile</param-name>
-      <param-value>%{ENGINE_MANUAL}/manual.properties</param-value>
+      <param-name>manualDir</param-name>
+      <param-value>%{ENGINE_MANUAL}</param-value>
     </init-param>
   </servlet>
   <servlet-mapping>
-      <servlet-name>HelpTagJsonServlet</servlet-name>
-      <url-pattern>/manual/helptag/*</url-pattern>
+      <servlet-name>ContextSensitiveHelpMappingServlet</servlet-name>
+      <url-pattern>/csh/*</url-pattern>
   </servlet-mapping>
 
   <!-- Filters -->
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/presenter/AbstractModelBoundPopupPresenterWidget.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/presenter/AbstractModelBoundPopupPresenterWidget.java
index 3c745c6..bc0e9d9 100644
--- 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/presenter/AbstractModelBoundPopupPresenterWidget.java
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/presenter/AbstractModelBoundPopupPresenterWidget.java
@@ -1,7 +1,7 @@
 package org.ovirt.engine.ui.common.presenter;
 
 import 
org.ovirt.engine.ui.common.presenter.popup.DefaultConfirmationPopupPresenterWidget;
-import org.ovirt.engine.ui.common.uicommon.DocumentationPathTranslator;
+import org.ovirt.engine.ui.common.uicommon.ContextSensitiveHelpManager;
 import org.ovirt.engine.ui.common.uicommon.model.DeferredModelCommandInvoker;
 import org.ovirt.engine.ui.common.uicommon.model.ModelBoundPopupHandler;
 import org.ovirt.engine.ui.common.uicommon.model.ModelBoundPopupResolver;
@@ -285,14 +285,14 @@
         UICommand openDocumentationCommand = 
model.getOpenDocumentationCommand();
         if (openDocumentationCommand != null) {
             boolean isDocumentationAvailable = hashName != null &&
-                    DocumentationPathTranslator.getPath(hashName) != null;
+                    ContextSensitiveHelpManager.getPath(hashName) != null;
             openDocumentationCommand.setIsAvailable(isDocumentationAvailable);
             updateHelpCommand(isDocumentationAvailable ? 
openDocumentationCommand : null);
         }
     }
 
     void updateHelpCommand(UICommand command) {
-        getView().setHelpCommand(command);
+        getView().setHelpCommand(command); // also sets the help icon visible
     }
 
     void updateItems(T model) {
@@ -334,12 +334,16 @@
         getView().stopProgress();
     }
 
+    /**
+     * Open the context-sensitive help for this model. Opens in a new window.
+     * ContextSensitiveHelpManager locates the proper URL via helptag/csh 
mapping file.
+     */
     protected void openDocumentation(T model) {
         String helpTag = model.getHelpTag().name;
-        String documentationPath = 
DocumentationPathTranslator.getPath(helpTag);
-        String documentationLibURL = 
model.getConfigurator().getDocumentationLibURL();
+        String documentationPath = 
ContextSensitiveHelpManager.getPath(helpTag);
+        String cshBase = model.getConfigurator().getDocsBaseUrl();
 
-        WebUtils.openUrlInNewWindow("_blank", documentationLibURL + 
documentationPath,  //$NON-NLS-1$
+        WebUtils.openUrlInNewWindow("_blank", cshBase + documentationPath,  
//$NON-NLS-1$
                 WebUtils.OPTION_SCROLLBARS);
     }
 
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/ContextSensitiveHelpManager.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/ContextSensitiveHelpManager.java
new file mode 100644
index 0000000..83915f2
--- /dev/null
+++ 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/ContextSensitiveHelpManager.java
@@ -0,0 +1,137 @@
+package org.ovirt.engine.ui.common.uicommon;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsonUtils;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.json.client.JSONObject;
+import com.google.gwt.json.client.JSONString;
+
+/**
+ * Responsible for translating helpTag mappings into context-sensitive help 
URLs.
+ *
+ * This class is initialized with a String of json that is read from the 
server.
+ * That json contains helptag-URL mappings for every locale that has the
+ * documentation package installed.
+ *
+ * It parses that json and caches csh mappings for the session. We first try to
+ * find csh for the user's locale. If that's not found, we fall back to 
English.
+ *
+ * @see ContextSensitiveHelpMappingServlet for the server-side portion of this 
operation,
+ * where the json is generated.
+ */
+public class ContextSensitiveHelpManager {
+
+    private static final Logger logger = 
Logger.getLogger(ContextSensitiveHelpManager.class.getName());
+
+    private final static String currentLocale = 
LocaleInfo.getCurrentLocale().getLocaleName().replaceAll("_", "-"); 
//$NON-NLS-1$ //$NON-NLS-2$
+    private final static String ENGLISH = "en-US"; //$NON-NLS-1$
+
+    private static Map<String, String> cshMappings = null;
+
+    // GWT overlay for the JSON object
+    private static class Mapping extends JavaScriptObject {
+        @SuppressWarnings("unused")
+        protected Mapping() {} // required for GWT
+    }
+
+    /**
+     * Get the csh path for a helpTag. Called by models / dialogs to see if 
this dialog has help available.
+     *
+     * @param helpTag
+     * @return URL to csh, or null if this dialog has no csh available.
+     */
+    public static String getPath(String helpTag) {
+
+        if (cshMappings == null || cshMappings.isEmpty()) {
+            // no documentation is installed
+            return null;
+        }
+
+        if (helpTag != null) {
+            return cshMappings.get(helpTag);
+        }
+
+        return null;
+    }
+
+    /**
+     * Get json mappings for the user's current locale, or fall back to English
+     * if user's locale is not installed.
+     *
+     * fileContent is a JSON object with this structure:
+     *
+     * {
+     *     'en-US' : {
+     *          'someHelpTag' : '/some/url.html'
+     *      },
+     *      'ja-JP' : {
+     *          'someHelpTag2' : '/some/url2.html'
+     *      },
+     *      etc.
+     * }
+     *
+     * @param fileContent
+     */
+    public static void init(String fileContent) {
+
+        Mapping jsonEval = JsonUtils.safeEval(fileContent);
+        JSONObject json = new JSONObject(jsonEval);
+
+        Map<String, Map<String, String>> detectedMappings = parseJson(json);
+
+        // all locale's mappings are now in detectedMappings. only need to 
save the user's locale's mappings, or English.
+
+        if (detectedMappings.isEmpty()) {
+            logger.info("No context-sensitive help was found on the server. It 
will not be available for this session."); //$NON-NLS-1$
+            return;
+        }
+
+        logger.info("Context-sensitive help is installed on the server. The 
following locales are available: " //$NON-NLS-1$
+                + detectedMappings.keySet());
+
+        if (detectedMappings.keySet().contains(currentLocale)) {
+            logger.info("Context-sensitive help for your locale, " + 
currentLocale + ", is installed and loaded."); //$NON-NLS-1$ //$NON-NLS-2$
+            cshMappings = detectedMappings.get(currentLocale);
+        }
+        else if (!currentLocale.equals(ENGLISH) && 
detectedMappings.keySet().contains(ENGLISH)) {
+            logger.info("Context-sensitive help wasn't found for your locale, 
" + currentLocale + ". Using English as a fallback."); //$NON-NLS-1$ 
//$NON-NLS-2$
+            cshMappings = detectedMappings.get(ENGLISH);
+        }
+        else {
+            logger.info("Context-sensitive help wasn't found for your locale, 
" + currentLocale + ", or the fallback locale, English. " //$NON-NLS-1$ 
//$NON-NLS-2$
+                    + "Context-sensitive help will not be available for this 
session."); //$NON-NLS-1$
+            cshMappings = null;
+        }
+    }
+
+    protected static Map<String, Map<String, String>> parseJson(JSONObject 
json) {
+
+        Map<String, Map<String, String>> detectedMappings = new HashMap<>();
+
+        for (String locale : json.keySet()) {
+
+            JSONObject localeObject = json.get(locale).isObject();
+            HashMap<String, String> localeMappings = new HashMap<>();
+
+            for (String docTag : localeObject.keySet()) {
+                JSONString urlString = localeObject.get(docTag).isString();
+                if (urlString != null && !docTag.isEmpty() && 
!urlString.stringValue().isEmpty()
+                        && !localeMappings.containsKey(docTag)) {
+
+                    localeMappings.put(docTag, urlString.stringValue());
+                }
+            }
+            // only add the locale to the Map if there were mappings.
+            // i.e. a locale with no mappings is treated as if it didn't exist.
+            if (!localeMappings.isEmpty()) {
+                detectedMappings.put(locale, localeMappings);
+            }
+        }
+
+        return detectedMappings;
+    }
+}
diff --git 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/DocumentationPathTranslator.java
 
b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/DocumentationPathTranslator.java
deleted file mode 100644
index a16c397..0000000
--- 
a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/uicommon/DocumentationPathTranslator.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.ovirt.engine.ui.common.uicommon;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsonUtils;
-import com.google.gwt.json.client.JSONObject;
-import com.google.gwt.json.client.JSONString;
-
-public class DocumentationPathTranslator {
-    private static Map<String, String> documentationPathMap;
-
-    // GWT overlay for the JSON object containing the help mappings
-    private static class Mapping extends JavaScriptObject {
-        @SuppressWarnings("unused")
-        protected Mapping() {} // required for GWT
-    }
-
-    public static String getPath(String helpTag) {
-        String path = null;
-
-        if (helpTag != null && documentationPathMap != null) {
-            path = documentationPathMap.get(helpTag);
-        }
-
-        return path;
-    }
-
-    public static void init(String fileContent) {
-
-        // fileContent is a JSON object with all unknown fields
-        Mapping mapping = JsonUtils.safeEval(fileContent);
-        JSONObject mappingJson = new JSONObject(mapping);
-
-        documentationPathMap = new HashMap<String, String>();
-
-        for (String docTag : mappingJson.keySet()) {
-            JSONString urlString = mappingJson.get(docTag).isString();
-            if (docTag != null && urlString != null && !docTag.isEmpty() &&
-                    !urlString.stringValue().isEmpty() && 
!documentationPathMap.containsKey(docTag)) {
-                documentationPathMap.put(docTag, urlString.stringValue());
-            }
-        }
-    }
-}
diff --git 
a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Configurator.java
 
b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Configurator.java
index 9454903..1421823 100644
--- 
a/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Configurator.java
+++ 
b/frontend/webadmin/modules/uicommonweb/src/main/java/org/ovirt/engine/ui/uicommonweb/Configurator.java
@@ -24,24 +24,21 @@
  */
 public abstract class Configurator {
 
-    private static final String DOCUMENTATION_LIB_PATH = "html/"; //$NON-NLS-1$
-    private static final String DOCUMENTATION_ROOT = 
BaseContextPathData.getInstance().getRelativePath()
-            + "docs/manual"; //$NON-NLS-1$
-    private static final String HELPTAG_MAPPING_ROOT = 
BaseContextPathData.getInstance().getRelativePath()
-            + "docs/manual/helptag"; //$NON-NLS-1$
+    private static final String DOCS_HTML_DIR = "html"; //$NON-NLS-1$
+    private static final String DOCS_ROOT = 
BaseContextPathData.getInstance().getRelativePath() + "docs/manual"; 
//$NON-NLS-1$
+    private static final String CSH_ROOT = 
BaseContextPathData.getInstance().getRelativePath() + "docs/csh"; //$NON-NLS-1$
+    private static final String JSON = ".json"; //$NON-NLS-1$
 
-    private static String documentationLangPath;
 
-    public static String getDocumentationLangPath() {
-        return documentationLangPath;
-    }
+    private static String localeDir;
 
     public Configurator() {
         // Set default configuration values
         setSpiceFullScreen(false);
 
-        String currentLocale = LocaleInfo.getCurrentLocale().getLocaleName();
-        documentationLangPath = currentLocale.replaceAll("_", "-") + "/"; 
//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+        String locale = LocaleInfo.getCurrentLocale().getLocaleName();
+        // doc package uses hyphens in the locale name dirs
+        localeDir = locale.replaceAll("_", "-"); //$NON-NLS-1$ //$NON-NLS-2$
 
         setSpiceVersion(new Version(4, 4));
         setSpiceDefaultUsbPort(32023);
@@ -174,33 +171,26 @@
     }
 
     /**
-     * Returns the base URL for serving documentation resources.
+     * Returns the base URL for serving documentation.
      * <p>
-     * Example: <code>http://www.example.com/docs/</code>
+     * Example: <code>https://<ovirt-engine>/docs/manual/en-US/html/</code>
      *
      * @return Documentation base URL, including the trailing slash.
      */
-    public String getDocumentationBaseURL() {
-        return FrontendUrlUtils.getRootURL() + DOCUMENTATION_ROOT + "/"; 
//$NON-NLS-1$
+    public String getDocsBaseUrl() {
+        return FrontendUrlUtils.getRootURL() + DOCS_ROOT + "/" + localeDir + 
"/" + DOCS_HTML_DIR + "/"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
     }
 
     /**
-     * Returns the base URL for serving helptag mapping files.
-     * @return helptag mapping base URL, including the trailing slash.
-     */
-    public String getHelpTagMappingBaseURL() {
-        return FrontendUrlUtils.getRootURL() + HELPTAG_MAPPING_ROOT + "/"; 
//$NON-NLS-1$
-    }
-
-    /**
-     * Returns the base URL for serving locale-specific HTML documentation.
+     * Returns the URL for serving the csh mapping file.
      * <p>
-     * Example: <code>http://www.example.com/docs/en-US/html/</code>
+     * Example: <code>https://<ovirt-engine>/docs/csh/webadmin.json</code>
+     *          <code>https://<ovirt-engine>/docs/csh/userportal.json</code>
      *
-     * @return Locale-specific HTML documentation base URL, including the 
trailing slash.
+     * @return the url
      */
-    public String getDocumentationLibURL() {
-        return getDocumentationBaseURL() + documentationLangPath + 
DOCUMENTATION_LIB_PATH;
+    public String getCshMappingUrl(String application) {
+        return FrontendUrlUtils.getRootURL() + CSH_ROOT + "/" + application + 
JSON; //$NON-NLS-1$
     }
 
     /**
@@ -243,7 +233,7 @@
 
     public boolean isWebSocketProxyDefined() {
         String wsConfig = (String) 
AsyncDataProvider.getInstance().getConfigValuePreConverted(ConfigurationValues.WebSocketProxy);
-        return wsConfig != null && !"".equals(wsConfig) && 
!"Off".equalsIgnoreCase(wsConfig); //$NON-NLS-1$ $NON-NLS-2$
+        return wsConfig != null && !"".equals(wsConfig) && 
!"Off".equalsIgnoreCase(wsConfig); //$NON-NLS-1$ //$NON-NLS-2$ $NON-NLS-2$
     }
 
     public static final class FileFetchEventArgs extends EventArgs {
@@ -321,7 +311,7 @@
     }
 
     public boolean isClientWindowsExplorer() {
-        return isClientWindows() && 
clientBrowserType().equalsIgnoreCase("Explorer"); //$NON-NLS-1$ //$NON-NLS-2$
+        return isClientWindows() && 
clientBrowserType().equalsIgnoreCase("Explorer"); //$NON-NLS-1$
     }
 
     public boolean isClientWindows() {
diff --git 
a/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/uicommon/UserPortalConfigurator.java
 
b/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/uicommon/UserPortalConfigurator.java
index 5a8a395..0a4b79e 100644
--- 
a/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/uicommon/UserPortalConfigurator.java
+++ 
b/frontend/webadmin/modules/userportal-gwtp/src/main/java/org/ovirt/engine/ui/userportal/uicommon/UserPortalConfigurator.java
@@ -4,7 +4,7 @@
 
 import org.ovirt.engine.core.compat.Version;
 import org.ovirt.engine.ui.common.uicommon.ClientAgentType;
-import org.ovirt.engine.ui.common.uicommon.DocumentationPathTranslator;
+import org.ovirt.engine.ui.common.uicommon.ContextSensitiveHelpManager;
 import org.ovirt.engine.ui.frontend.AsyncQuery;
 import org.ovirt.engine.ui.frontend.INewAsyncCallback;
 import org.ovirt.engine.ui.frontend.utils.BaseContextPathData;
@@ -23,7 +23,7 @@
 
 public class UserPortalConfigurator extends Configurator implements 
IEventListener<Configurator.FileFetchEventArgs> {
 
-    public static final String DOCUMENTATION_GUIDE_PATH = 
"User_Portal_Guide/index.html"; //$NON-NLS-1$
+    public static final String APPLICATION_NAME = "userportal"; //$NON-NLS-1$
 
     public EventDefinition spiceVersionFileFetchedEvent_Definition =
             new EventDefinition("spiceVersionFileFetched", 
UserPortalConfigurator.class); //$NON-NLS-1$
@@ -46,7 +46,8 @@
         super();
         this.placeManager = placeManager;
         this.clientAgentType = clientAgentType;
-        fetchDocumentationFile();
+
+        prepareContextSensitiveHelp();
 
         // This means that it is UserPortal application.
         setIsAdmin(false);
@@ -62,6 +63,11 @@
 
         // Update Spice version if needed
         updateSpiceVersion();
+    }
+
+    protected void prepareContextSensitiveHelp() {
+        fetchFile(getCshMappingUrl(APPLICATION_NAME), 
documentationFileFetchedEvent);
+        // async callback calls ContextSensitiveHelpManager.init
     }
 
     public void updateUsbFilter() {
@@ -102,8 +108,8 @@
             Version spiceVersion = parseVersion(args.getFileContent());
             setSpiceVersion(spiceVersion);
         } else if 
(ev.matchesDefinition(documentationFileFetchedEvent_Definition)) {
-            String documentationPathFileContent = args.getFileContent();
-            DocumentationPathTranslator.init(documentationPathFileContent);
+            String cshMapping = args.getFileContent();
+            ContextSensitiveHelpManager.init(cshMapping);
         } else if (ev.matchesDefinition(usbFilterFileFetchedEvent_Definition)) 
{
             String usbFilter = args.getFileContent();
             setUsbFilter(usbFilter);
@@ -142,10 +148,4 @@
     public Float clientBrowserVersion() {
         return clientAgentType.version;
     }
-
-    protected void fetchDocumentationFile() {
-        // TODO: don't hardcode userportal application name here
-        fetchFile(getHelpTagMappingBaseURL() + "userportal.json", 
documentationFileFetchedEvent); //$NON-NLS-1$
-    }
-
 }
diff --git 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/WebAdminConfigurator.java
 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/WebAdminConfigurator.java
index c47fda7..79e5242 100644
--- 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/WebAdminConfigurator.java
+++ 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/uicommon/WebAdminConfigurator.java
@@ -4,7 +4,7 @@
 
 import org.ovirt.engine.core.compat.Version;
 import org.ovirt.engine.ui.common.uicommon.ClientAgentType;
-import org.ovirt.engine.ui.common.uicommon.DocumentationPathTranslator;
+import org.ovirt.engine.ui.common.uicommon.ContextSensitiveHelpManager;
 import org.ovirt.engine.ui.uicommonweb.Configurator;
 import org.ovirt.engine.ui.uicommonweb.models.vms.ISpice;
 import org.ovirt.engine.ui.uicommonweb.models.vms.WANDisableEffects;
@@ -17,7 +17,7 @@
 
 public class WebAdminConfigurator extends Configurator implements 
IEventListener<Configurator.FileFetchEventArgs> {
 
-    public static final String DOCUMENTATION_GUIDE_PATH = 
"Administration_Guide/index.html"; //$NON-NLS-1$
+    public static final String APPLICATION_NAME = "webadmin"; //$NON-NLS-1$
 
     public EventDefinition spiceVersionFileFetchedEvent_Definition =
             new EventDefinition("spiceVersionFileFetched", 
WebAdminConfigurator.class); //$NON-NLS-1$
@@ -34,7 +34,8 @@
         super();
         this.clientAgentType = clientAgentType;
 
-        fetchDocumentationFile();
+        prepareContextSensitiveHelp();
+
         // This means that this is WebAdmin application.
         setIsAdmin(true);
         setSpiceAdminConsole(true);
@@ -47,14 +48,19 @@
         updateSpiceVersion();
     }
 
+    protected void prepareContextSensitiveHelp() {
+        fetchFile(getCshMappingUrl(APPLICATION_NAME), 
documentationFileFetchedEvent);
+        // async callback calls ContextSensitiveHelpManager.init
+    }
+
     @Override
     public void eventRaised(Event<? extends FileFetchEventArgs> ev, Object 
sender, FileFetchEventArgs args) {
         if (ev.matchesDefinition(spiceVersionFileFetchedEvent_Definition)) {
             Version spiceVersion = parseVersion(args.getFileContent());
             setSpiceVersion(spiceVersion);
         } else if 
(ev.matchesDefinition(documentationFileFetchedEvent_Definition)) {
-            String documentationPathFileContent = args.getFileContent();
-            DocumentationPathTranslator.init(documentationPathFileContent);
+            String cshMapping = args.getFileContent();
+            ContextSensitiveHelpManager.init(cshMapping);
         }
     }
 
@@ -88,11 +94,6 @@
     @Override
     public Float clientBrowserVersion() {
         return clientAgentType.version;
-    }
-
-    protected void fetchDocumentationFile() {
-        // TODO: don't hard code webadmin application name here
-        fetchFile(getHelpTagMappingBaseURL() + "webadmin.json", 
documentationFileFetchedEvent); //$NON-NLS-1$
     }
 
 }


-- 
To view, visit https://gerrit.ovirt.org/39297
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I1f854a9d7034d436730a4e82c88963a854e2c2b6
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Greg Sheremeta <gsher...@redhat.com>
_______________________________________________
Engine-patches mailing list
Engine-patches@ovirt.org
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to