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