mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUAC-587: Manage languages within own service. Allow dynamic addition of languages.
This commit is contained in:
@@ -95,6 +95,11 @@ public class ExtensionModule extends ServletModule {
|
||||
*/
|
||||
private Class<? extends AuthenticationProvider> boundAuthenticationProvider = null;
|
||||
|
||||
/**
|
||||
* Service for adding and retrieving language resources.
|
||||
*/
|
||||
private final LanguageResourceService languageResourceService = new LanguageResourceService();
|
||||
|
||||
/**
|
||||
* Returns the classloader that should be used as the parent classloader
|
||||
* for all extensions. If the GUACAMOLE_HOME/lib directory exists, this
|
||||
@@ -308,6 +313,12 @@ public class ExtensionModule extends ServletModule {
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
|
||||
// Bind language resource service
|
||||
bind(LanguageResourceService.class).toInstance(languageResourceService);
|
||||
|
||||
// Load initial language resources from servlet context
|
||||
languageResourceService.addLanguageResources(getServletContext());
|
||||
|
||||
// Load authentication provider from guacamole.properties for sake of backwards compatibility
|
||||
Class<AuthenticationProvider> authProviderProperty = getAuthProviderProperty();
|
||||
if (authProviderProperty != null)
|
||||
|
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.net.basic.extension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.ServletContext;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.glyptodon.guacamole.net.basic.resource.Resource;
|
||||
import org.glyptodon.guacamole.net.basic.resource.WebApplicationResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service which provides access to all built-in languages as resources, and
|
||||
* allows other resources to be added or overlaid against existing resources.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class LanguageResourceService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(LanguageResourceService.class);
|
||||
|
||||
/**
|
||||
* The path to the translation folder within the webapp.
|
||||
*/
|
||||
private static final String TRANSLATION_PATH = "/translations";
|
||||
|
||||
/**
|
||||
* The JSON property for the human readable display name.
|
||||
*/
|
||||
private static final String LANGUAGE_DISPLAY_NAME_KEY = "NAME";
|
||||
|
||||
/**
|
||||
* The Jackson parser for parsing the language JSON files.
|
||||
*/
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* The regular expression to use for parsing the language key from the
|
||||
* filename.
|
||||
*/
|
||||
private static final Pattern LANGUAGE_KEY_PATTERN = Pattern.compile(".*/([a-z]+_[A-Z]+)\\.json");
|
||||
|
||||
/**
|
||||
* Map of all language resources by language key. Language keys are
|
||||
* language and country code pairs, separated by an underscore, like
|
||||
* "en_US".
|
||||
*/
|
||||
private final Map<String, Resource> resources = new HashMap<String, Resource>();
|
||||
|
||||
/**
|
||||
* Derives a language key from the filename within the given path, if
|
||||
* possible. If the filename is not a valid language key, null is returned.
|
||||
*
|
||||
* @param path
|
||||
* The path containing the filename to derive the language key from.
|
||||
*
|
||||
* @return
|
||||
* The derived language key, or null if the filename is not a valid
|
||||
* language key.
|
||||
*/
|
||||
public String getLanguageKey(String path) {
|
||||
|
||||
// Parse language key from filename
|
||||
Matcher languageKeyMatcher = LANGUAGE_KEY_PATTERN.matcher(path);
|
||||
if (!languageKeyMatcher.matches())
|
||||
return null;
|
||||
|
||||
// Return parsed key
|
||||
return languageKeyMatcher.group(1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or overlays the given language resource, which need not exist in
|
||||
* the ServletContext. If a language resource is already defined for the
|
||||
* given language key, the strings from the given resource will be overlaid
|
||||
* on top of the existing strings, augmenting or overriding the available
|
||||
* strings for that language.
|
||||
*
|
||||
* @param key
|
||||
* The language key of the resource being added. Language keys are
|
||||
* pairs consisting of a language code followed by an underscore and
|
||||
* country code, such as "en_US".
|
||||
*
|
||||
* @param resource
|
||||
* The language resource to add. This resource must have the mimetype
|
||||
* "application/json".
|
||||
*/
|
||||
public void addLanguageResource(String key, Resource resource) {
|
||||
resources.put(key, resource);
|
||||
logger.debug("Added language: \"{}\"", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or overlays all languages defined within the /translations
|
||||
* directory of the given ServletContext. If no such language files exist,
|
||||
* nothing is done. If a language is already defined, the strings from the
|
||||
* will be overlaid on top of the existing strings, augmenting or
|
||||
* overriding the available strings for that language. The language key
|
||||
* for each language file is derived from the filename.
|
||||
*
|
||||
* @param context
|
||||
* The ServletContext from which language files should be loaded.
|
||||
*/
|
||||
public void addLanguageResources(ServletContext context) {
|
||||
|
||||
// Get the paths of all the translation files
|
||||
Set<?> resourcePaths = context.getResourcePaths(TRANSLATION_PATH);
|
||||
|
||||
// If no translation files found, nothing to add
|
||||
if (resourcePaths == null)
|
||||
return;
|
||||
|
||||
// Iterate through all the found language files and add them to the map
|
||||
for (Object resourcePathObject : resourcePaths) {
|
||||
|
||||
// Each resource path is guaranteed to be a string
|
||||
String resourcePath = (String) resourcePathObject;
|
||||
|
||||
// Parse language key from path
|
||||
String languageKey = getLanguageKey(resourcePath);
|
||||
if (languageKey == null) {
|
||||
logger.warn("Invalid language file name: \"{}\"", resourcePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add/overlay new resource
|
||||
addLanguageResource(
|
||||
languageKey,
|
||||
new WebApplicationResource(context, "application/json", resourcePath)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of all unique language keys currently associated with
|
||||
* language resources stored in this service. The returned set cannot be
|
||||
* modified.
|
||||
*
|
||||
* @return
|
||||
* A set of all unique language keys currently associated with this
|
||||
* service.
|
||||
*/
|
||||
public Set<String> getLanguageKeys() {
|
||||
return Collections.unmodifiableSet(resources.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all languages currently associated with this service,
|
||||
* where the key of each map entry is the language key. The returned map
|
||||
* cannot be modified.
|
||||
*
|
||||
* @return
|
||||
* A map of all languages currently associated with this service.
|
||||
*/
|
||||
public Map<String, Resource> getLanguageResources() {
|
||||
return Collections.unmodifiableMap(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping of all language keys to their corresponding human-
|
||||
* readable language names. If an error occurs while parsing a language
|
||||
* resource, its key/name pair will simply be omitted. The returned map
|
||||
* cannot be modified.
|
||||
*
|
||||
* @return
|
||||
* A map of all language keys and their corresponding human-readable
|
||||
* names.
|
||||
*/
|
||||
public Map<String, String> getLanguageNames() {
|
||||
|
||||
Map<String, String> languageNames = new HashMap<String, String>();
|
||||
|
||||
// For each language key/resource pair
|
||||
for (Map.Entry<String, Resource> entry : resources.entrySet()) {
|
||||
|
||||
// Get language key and resource
|
||||
String languageKey = entry.getKey();
|
||||
Resource resource = entry.getValue();
|
||||
|
||||
// Get stream for resource
|
||||
InputStream resourceStream = resource.asStream();
|
||||
if (resourceStream == null) {
|
||||
logger.warn("Expected language resource does not exist: \"{}\".", languageKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get name node of language
|
||||
try {
|
||||
JsonNode tree = mapper.readTree(resourceStream);
|
||||
JsonNode nameNode = tree.get(LANGUAGE_DISPLAY_NAME_KEY);
|
||||
|
||||
// Attempt to read language name from node
|
||||
String languageName;
|
||||
if (nameNode == null || (languageName = nameNode.getTextValue()) == null) {
|
||||
logger.warn("Root-level \"" + LANGUAGE_DISPLAY_NAME_KEY + "\" string missing or invalid in language \"{}\"", languageKey);
|
||||
languageName = languageKey;
|
||||
}
|
||||
|
||||
// Add language key/name pair to map
|
||||
languageNames.put(languageKey, languageName);
|
||||
|
||||
}
|
||||
|
||||
// Continue with next language if unable to read
|
||||
catch (IOException e) {
|
||||
logger.warn("Unable to read language resource \"{}\".", languageKey);
|
||||
logger.debug("Error reading language resource.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(languageNames);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -22,27 +22,13 @@
|
||||
|
||||
package org.glyptodon.guacamole.net.basic.rest.language;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.glyptodon.guacamole.net.basic.extension.LanguageResourceService;
|
||||
|
||||
|
||||
/**
|
||||
@@ -55,108 +41,23 @@ import org.slf4j.LoggerFactory;
|
||||
public class LanguageRESTService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
* Service for retrieving information regarding available language
|
||||
* resources.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(LanguageRESTService.class);
|
||||
|
||||
/**
|
||||
* The path to the translation folder within the webapp.
|
||||
*/
|
||||
private static final String TRANSLATION_PATH = "/translations";
|
||||
|
||||
/**
|
||||
* The JSON property for the human readable display name.
|
||||
*/
|
||||
private static final String LANGUAGE_DISPLAY_NAME_KEY = "NAME";
|
||||
|
||||
/**
|
||||
* The Jackson parser for parsing the language JSON files.
|
||||
*/
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* The regular expression to use for parsing the language key from the
|
||||
* filename.
|
||||
*/
|
||||
private static final Pattern LANGUAGE_KEY_PATTERN = Pattern.compile(".*/([a-z]+_[A-Z]+)\\.json");
|
||||
@Inject
|
||||
private LanguageResourceService languageResourceService;
|
||||
|
||||
/**
|
||||
* Returns a map of all available language keys to their corresponding
|
||||
* human-readable names.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param servletContext
|
||||
* The ServletContext associated with the request.
|
||||
*
|
||||
* @return
|
||||
* A map of languages defined in the system, of language key to
|
||||
* display name.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the available languages.
|
||||
*/
|
||||
@GET
|
||||
@AuthProviderRESTExposure
|
||||
public Map<String, String> getLanguages(@QueryParam("token") String authToken,
|
||||
@Context ServletContext servletContext) throws GuacamoleException {
|
||||
|
||||
// Get the paths of all the translation files
|
||||
Set<?> resourcePaths = servletContext.getResourcePaths(TRANSLATION_PATH);
|
||||
|
||||
// If no translation files found, return an empty map
|
||||
if (resourcePaths == null)
|
||||
return Collections.<String, String>emptyMap();
|
||||
|
||||
Map<String, String> languageMap = new HashMap<String, String>();
|
||||
|
||||
// Iterate through all the found language files and add them to the return map
|
||||
for (Object resourcePathObject : resourcePaths) {
|
||||
|
||||
// Each resource path is guaranteed to be a string
|
||||
String resourcePath = (String) resourcePathObject;
|
||||
|
||||
// Get input stream for language file
|
||||
InputStream languageFileStream = servletContext.getResourceAsStream(resourcePath);
|
||||
if (languageFileStream == null) {
|
||||
logger.warn("Unable to read language resource \"{}\"", resourcePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Parse language key from filename
|
||||
String languageKey;
|
||||
Matcher languageKeyMatcher = LANGUAGE_KEY_PATTERN.matcher(resourcePath);
|
||||
if (!languageKeyMatcher.matches() || (languageKey = languageKeyMatcher.group(1)) == null) {
|
||||
logger.warn("Invalid language file name: \"{}\"", resourcePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get name node of language
|
||||
JsonNode tree = mapper.readTree(languageFileStream);
|
||||
JsonNode nameNode = tree.get(LANGUAGE_DISPLAY_NAME_KEY);
|
||||
|
||||
// Attempt to read language name from node
|
||||
String languageName;
|
||||
if (nameNode == null || (languageName = nameNode.getTextValue()) == null) {
|
||||
logger.warn("Root-level \"" + LANGUAGE_DISPLAY_NAME_KEY + "\" string missing or invalid in language file \"{}\"", resourcePath);
|
||||
languageName = languageKey;
|
||||
}
|
||||
|
||||
// Add language key/name pair to map
|
||||
languageMap.put(languageKey, languageName);
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Unable to read language resource \"{}\": {}", resourcePath, e.getMessage());
|
||||
logger.debug("Error while reading language resource.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return languageMap;
|
||||
public Map<String, String> getLanguages() {
|
||||
return languageResourceService.getLanguageNames();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user