/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.controller.descriptions;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPRECATED;
import java.lang.ref.WeakReference;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/**
* {@link ResourceBundle} based {@link ResourceDescriptionResolver} that builds resource bundle
* keys by taking a "key prefix" provided to the constructor and dot-appending the string params to the
* various {@code getXXXDescription} methods. The "key prefix" is associated with a particular resource and serves
* to provide a namespace for the resource's key's in the set of keys used by a {@link ResourceBundle}.
* <p>
* Code that uses this class to localize text descriptions should store their text description in a properties
* file whose keys follow the following format, where "prefix" is the {@code keyPrefix} param passed to the constructor,
* "attribute-name" is the name of an attribute, "operation-name" is the name of an operation, "param-name" is the
* name of a parameter to an operation, "child-type" is the name of one of the resource's valid child types, and
* "value-type-suffix" is the name of some detail element in a parameter, attribute or operation reply value that has a
* complex type.
* </p>
* <p>
* prefix=The description of the resource
* prefix.attribute-name=The description of one of the resource's attributes.
* prefix.attribute-name.value-type-suffix=The description of an element in a complex attribute's {@link ModelDescriptionConstants#VALUE_TYPE}.
* prefix.operation-name=The description of one of the resource's operations.
* prefix.operation-name.param-name=The description of one of an operation's parameters.
* prefix.operation-name.param-name.value-type-suffix=The description of an element in a complex operation parameter's {@link ModelDescriptionConstants#VALUE_TYPE}.
* prefix.operation-name.reply=The description of an operation's reply value.
* prefix.operation-name.reply.value-type-suffix=The description of an element in a complex operation reply value's {@link ModelDescriptionConstants#VALUE_TYPE}.
* prefix.child-type=The description of one of the resource's child resource types.
* </p>
* <p>
* The constructor supports two settings designed to help minimize the need for redundant entries in the properties file:
* <ol>
* <li>{@code reuseAttributesForAdd} affects how the {@code getOperationParameter...} methods work. If {@code true},
* the assumption is that for an operation named "add" the text description of a parameter will be the same as
* the description of an attribute of the same name. This would allow the properties for this example resource:
* <p>
* pool.min-size=The minimum pool size.
* pool.max-size=The maximum pool size.
* pool.add.min-size=The minimum pool size.
* pool.add.max-size=The maximum pool size.
* </p>
* <p>To be reduced to:</p>
* <p>
* pool.min-size=The minimum pool size.
* pool.max-size=The maximum pool size.
* </p>
*
* </li>
*
* <li>{@code useUnprefixedChildTypes} affects how the {@link #getChildTypeDescription(String, Locale, ResourceBundle)}
* method works. The descriptions of a set of related resources need to include a description in the parent resource
* of its relationship to the child resource, as well as the description of the child resource itself. These two
* descriptions are often included in the same properties file and may be the exact same text. If {@code useUnprefixedChildTypes}
* is {@code true}, {@code getChildTypeDescription(...)} will assume there is an entry in the properties file
* that exactly matches the name of the child type. This would allow the properties for this example set of resources:
* <p>
* subsystem=The foo subsystem.
* ... attributes and operations of the "subsystem" resource
* subsystem.connector=A connector that can be used to access the foo.
* connector=A connector that can be used to access the foo.
* ... attributes and operations of the "connector" resource
* </p>
* <p>To be reduced to:</p>
* <p>
* subsystem=The foo subsystem.
* ... attributes and operations of the "subsystem" resource
* connector=A connector that can be used to access the foo.
* connector=A connector that can be used to access the foo.
* ... attributes and operations of the "connector" resource
* </p>
* <p>Note that while this kind of usage is convenient, it often results in slightly lower quality descriptions. For example,
* in the example above, a better description for "subsystem.connector" is "The connectors that can be used to access the foo."</p>
* </li>
* </ol>
* </p>
*
* @author Brian Stansberry (c) 2011 Red Hat Inc.
*/
public class StandardResourceDescriptionResolver implements ResourceDescriptionResolver {
/**
* Additional string dot-appended to the key by the
* {@link StandardResourceDescriptionResolver#getOperationReplyDescription(String, Locale, ResourceBundle)} method.
*/
public static final String REPLY = "reply";
private final String keyPrefix;
private final String bundleBaseName;
private final WeakReference<ClassLoader> bundleLoader;
private final boolean reuseAttributesForAdd;
private final boolean useUnprefixedChildTypes;
public StandardResourceDescriptionResolver(final String keyPrefix,
final String bundleBaseName,
final ClassLoader bundleLoader) {
this(keyPrefix, bundleBaseName, bundleLoader, false, false);
}
public StandardResourceDescriptionResolver(final String keyPrefix,
final String bundleBaseName,
final ClassLoader bundleLoader,
final boolean reuseAttributesForAdd,
final boolean useUnprefixedChildTypes) {
this.keyPrefix = keyPrefix;
this.bundleBaseName = bundleBaseName;
this.bundleLoader = new WeakReference<ClassLoader>(bundleLoader);
this.reuseAttributesForAdd = reuseAttributesForAdd;
this.useUnprefixedChildTypes = useUnprefixedChildTypes;
}
public String getKeyPrefix() {
return keyPrefix;
}
public boolean isReuseAttributesForAdd() {
return reuseAttributesForAdd;
}
public boolean isUseUnprefixedChildTypes() {
return useUnprefixedChildTypes;
}
public StandardResourceDescriptionResolver getChildResolver(String key){
return new StandardResourceDescriptionResolver(keyPrefix+"."+key,bundleBaseName,bundleLoader.get(),reuseAttributesForAdd,useUnprefixedChildTypes);
}
/** {@inheritDoc} */
@Override
public ResourceBundle getResourceBundle(Locale locale) {
if (locale == null) {
locale = Locale.getDefault();
}
return ResourceBundle.getBundle(bundleBaseName, locale, bundleLoader.get());
}
/** {@inheritDoc} */
@Override
public String getResourceDescription(Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey());
}
/** {@inheritDoc} */
@Override
public String getResourceAttributeDescription(String attributeName, Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey(attributeName));
}
/** {@inheritDoc} */
@Override
public String getResourceAttributeValueTypeDescription(String attributeName, Locale locale, ResourceBundle bundle, String... suffixes) {
return bundle.getString(getVariableBundleKey(new String[]{attributeName}, suffixes));
}
/** {@inheritDoc} */
@Override
public String getOperationDescription(String operationName, Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey(operationName));
}
/** {@inheritDoc} */
@Override
public String getOperationParameterDescription(String operationName, String paramName, Locale locale, ResourceBundle bundle) {
if (reuseAttributesForAdd && ADD.equals(operationName)) {
return bundle.getString(getBundleKey(paramName));
}
return bundle.getString(getBundleKey(operationName, paramName));
}
/** {@inheritDoc} */
@Override
public String getOperationParameterValueTypeDescription(String operationName, String paramName, Locale locale, ResourceBundle bundle, String... suffixes) {
String[] fixed;
if (reuseAttributesForAdd && ADD.equals(operationName)) {
fixed = new String[]{paramName};
} else {
fixed = new String[]{operationName, paramName};
}
return bundle.getString(getVariableBundleKey(fixed, suffixes));
}
/** {@inheritDoc} */
@Override
public String getOperationReplyDescription(String operationName, Locale locale, ResourceBundle bundle) {
try {
return bundle.getString(getBundleKey(operationName, REPLY));
} catch (MissingResourceException e) {
return null;
}
}
/** {@inheritDoc} */
@Override
public String getOperationReplyValueTypeDescription(String operationName, Locale locale, ResourceBundle bundle, String... suffixes) {
try {
return bundle.getString(getVariableBundleKey(new String[]{operationName, REPLY}, suffixes));
} catch (MissingResourceException e) {
try {
return getOperationParameterValueTypeDescription(operationName, suffixes[0], locale, bundle);
} catch (MissingResourceException ex) {
throw e;
}
}
}
/** {@inheritDoc} */
@Override
public String getNotificationDescription(String notificationType, Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey(notificationType));
}
/** {@inheritDoc} */
@Override
public String getChildTypeDescription(String childType, Locale locale, ResourceBundle bundle) {
final String bundleKey = useUnprefixedChildTypes ? childType : getBundleKey(childType);
return bundle.getString(bundleKey);
}
/**
* {@inheritDoc}
*/
@Override
public String getResourceDeprecatedDescription(Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey(DEPRECATED));
}
/**
* {@inheritDoc}
*/
@Override
public String getResourceAttributeDeprecatedDescription(String attributeName, Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey(attributeName, DEPRECATED));
}
/**
* {@inheritDoc}
*/
@Override
public String getOperationDeprecatedDescription(String operationName, Locale locale, ResourceBundle bundle) {
return bundle.getString(getBundleKey(operationName, DEPRECATED));
}
/**
* {@inheritDoc}
*/
@Override
public String getOperationParameterDeprecatedDescription(String operationName, String paramName, Locale locale, ResourceBundle bundle) {
if (reuseAttributesForAdd && ADD.equals(operationName)) {
return bundle.getString(getBundleKey(paramName,DEPRECATED));
}
return bundle.getString(getBundleKey(operationName, paramName,DEPRECATED));
}
protected String getBundleKey(String... args) {
return getVariableBundleKey(args);
}
private String getVariableBundleKey(String[] fixed, String... variable) {
StringBuilder sb = new StringBuilder(keyPrefix);
for (String arg : fixed) {
if (sb.length() > 0) {
sb.append('.');
}
sb.append(arg);
}
if (variable != null) {
for (String arg : variable) {
if (sb.length() > 0) {
sb.append('.');
}
sb.append(arg);
}
}
return sb.toString();
}
}