blob: a1687c420c3ffe7e28259ff34cc58fd99e2f5e2a [file]
/*
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.xml.internal;
import com.sun.org.apache.xerces.internal.util.SecurityManager;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import javax.xml.catalog.CatalogManager;
import javax.xml.catalog.CatalogResolver;
import javax.xml.catalog.CatalogResolver.NotFoundAction;
import javax.xml.stream.XMLInputFactory;
import jdk.xml.internal.JdkProperty.State;
import jdk.xml.internal.JdkProperty.ImplPropMap;
import org.xml.sax.SAXException;
/**
* This class manages standard and implementation-specific limitations.
*
*/
public final class XMLSecurityManager implements Cloneable {
public static final String DTD_KEY = JdkConstants.DTD_PROPNAME;
// Xerces Feature
public static final String DISALLOW_DTD = "http://apache.org/xml/features/disallow-doctype-decl";
public static final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
// StAX feature
public static final String ZEPHYR_PROPERTY_PREFIX = "http://java.sun.com/xml/stream/properties/" ;
public static final String IGNORE_EXTERNAL_DTD = ZEPHYR_PROPERTY_PREFIX + "ignore-external-dtd";
// Valid values for the DTD property
public static final String DTD_ALLOW = "allow";
public static final String DTD_IGNORE = "ignore";
public static final String DTD_DENY = "deny";
static final Map<String, Integer> DTD_MAP;
// Source Level JDK 8
static {
Map<String, Integer> map = new HashMap<>();
map.put(DTD_ALLOW, 0);
map.put(DTD_IGNORE, 1);
map.put(DTD_DENY, 2);
DTD_MAP = Collections.unmodifiableMap(map);
}
// Valid values for Catalog Resolve, and mappings between the string and
// interger values
static final Map<String, Integer> CR_MAP;
// Source Level JDK 8
static {
Map<String, Integer> map = new HashMap<>();
map.put("continue", 0);
map.put("ignore", 1);
map.put("strict", 2);
CR_MAP = Collections.unmodifiableMap(map);
}
// Value converter for properties of type Boolean
private static final BooleanMapper BOOLMAPPER = new BooleanMapper();
// Value converter for properties of type Integer
private static final IntegerMapper INTMAPPER = new IntegerMapper();
// DTD value mapper
private static final StringMapper DTDMAPPER = new StringMapper(DTD_MAP);
// Catalog Resolve value mapper
private static final StringMapper CRMAPPER = new StringMapper(CR_MAP);
/**
* Limits managed by the security manager
*/
@SuppressWarnings("deprecation")
public static enum Limit {
ENTITY_EXPANSION_LIMIT("EntityExpansionLimit", JdkConstants.JDK_ENTITY_EXPANSION_LIMIT,
JdkConstants.SP_ENTITY_EXPANSION_LIMIT, JdkConstants.ENTITY_EXPANSION_LIMIT, 2500, 2500, Processor.PARSER, INTMAPPER),
MAX_OCCUR_NODE_LIMIT("MaxOccurLimit", JdkConstants.JDK_MAX_OCCUR_LIMIT,
JdkConstants.SP_MAX_OCCUR_LIMIT, JdkConstants.MAX_OCCUR_LIMIT, 5000, 5000, Processor.PARSER, INTMAPPER),
ELEMENT_ATTRIBUTE_LIMIT("ElementAttributeLimit", JdkConstants.JDK_ELEMENT_ATTRIBUTE_LIMIT,
JdkConstants.SP_ELEMENT_ATTRIBUTE_LIMIT, JdkConstants.ELEMENT_ATTRIBUTE_LIMIT, 200, 200, Processor.PARSER, INTMAPPER),
TOTAL_ENTITY_SIZE_LIMIT("TotalEntitySizeLimit", JdkConstants.JDK_TOTAL_ENTITY_SIZE_LIMIT,
JdkConstants.SP_TOTAL_ENTITY_SIZE_LIMIT, null, 100000, 100000, Processor.PARSER, INTMAPPER),
GENERAL_ENTITY_SIZE_LIMIT("MaxEntitySizeLimit", JdkConstants.JDK_GENERAL_ENTITY_SIZE_LIMIT,
JdkConstants.SP_GENERAL_ENTITY_SIZE_LIMIT, null, 100000, 100000, Processor.PARSER, INTMAPPER),
PARAMETER_ENTITY_SIZE_LIMIT("MaxParameterEntitySizeLimit", JdkConstants.JDK_PARAMETER_ENTITY_SIZE_LIMIT,
JdkConstants.SP_PARAMETER_ENTITY_SIZE_LIMIT, null, 15000, 15000, Processor.PARSER, INTMAPPER),
MAX_ELEMENT_DEPTH_LIMIT("MaxElementDepthLimit", JdkConstants.JDK_MAX_ELEMENT_DEPTH,
JdkConstants.SP_MAX_ELEMENT_DEPTH, null, 100, 100, Processor.PARSER, INTMAPPER),
MAX_NAME_LIMIT("MaxXMLNameLimit", JdkConstants.JDK_XML_NAME_LIMIT,
JdkConstants.SP_XML_NAME_LIMIT, null, 1000, 1000, Processor.PARSER, INTMAPPER),
ENTITY_REPLACEMENT_LIMIT("EntityReplacementLimit", JdkConstants.JDK_ENTITY_REPLACEMENT_LIMIT,
JdkConstants.SP_ENTITY_REPLACEMENT_LIMIT, null, 100000, 100000, Processor.PARSER, INTMAPPER),
XPATH_GROUP_LIMIT("XPathGroupLimit", JdkConstants.XPATH_GROUP_LIMIT,
JdkConstants.XPATH_GROUP_LIMIT, null, 10, 10, Processor.XPATH, INTMAPPER),
XPATH_OP_LIMIT("XPathExprOpLimit", JdkConstants.XPATH_OP_LIMIT,
JdkConstants.XPATH_OP_LIMIT, null, 100, 100, Processor.XPATH, INTMAPPER),
XPATH_TOTALOP_LIMIT("XPathTotalOpLimit", JdkConstants.XPATH_TOTALOP_LIMIT,
JdkConstants.XPATH_TOTALOP_LIMIT, null, 10000, 10000, Processor.XPATH, INTMAPPER),
DTD("DTDProperty", JdkConstants.DTD_PROPNAME, JdkConstants.DTD_PROPNAME, null,
JdkConstants.ALLOW, JdkConstants.ALLOW, Processor.PARSER, DTDMAPPER),
XERCES_DISALLOW_DTD("disallowDTD", DISALLOW_DTD, null, null, 0, 0, Processor.PARSER, BOOLMAPPER),
STAX_SUPPORT_DTD("supportDTD", XMLInputFactory.SUPPORT_DTD, null, null, 1, 1, Processor.PARSER, BOOLMAPPER),
JDKCATALOG_RESOLVE("JDKCatalogResolve", JdkConstants.JDKCATALOG_RESOLVE, JdkConstants.JDKCATALOG_RESOLVE, null,
JdkConstants.CONTINUE, JdkConstants.CONTINUE, Processor.PARSER, CRMAPPER),
;
final String key;
final String apiProperty;
final String systemProperty;
final String spOld;
final int defaultValue;
final int secureValue;
final Processor processor;
final ValueMapper mapper;
Limit(String key, String apiProperty, String systemProperty, String spOld, int value,
int secureValue, Processor processor, ValueMapper mapper) {
this.key = key;
this.apiProperty = apiProperty;
this.systemProperty = systemProperty;
this.spOld = spOld;
this.defaultValue = value;
this.secureValue = secureValue;
this.processor = processor;
this.mapper = mapper;
}
/**
* Checks whether the specified name is a limit. Checks both the
* property and System Property which is now the new property name.
*
* @param name the specified name
* @return true if there is a match, false otherwise
*/
public boolean is(String name) {
// current spec: new property name == systemProperty
return (systemProperty != null && systemProperty.equals(name)) ||
// current spec: apiProperty is legacy
(apiProperty.equals(name));
}
/**
* Returns the state of a property name. By the specification as of JDK 17,
* the "jdk.xml." prefixed System property name is also the current API
* name. The URI-based qName is legacy.
*
* @param name the property name
* @return the state of the property name, null if no match
*/
public State getState(String name) {
if (systemProperty != null && systemProperty.equals(name)) {
return State.APIPROPERTY;
} else if (apiProperty.equals(name)) {
//the URI-style qName is legacy
return State.LEGACY_APIPROPERTY;
}
return null;
}
public String key() {
return key;
}
public String apiProperty() {
return apiProperty;
}
public String systemProperty() {
return systemProperty;
}
// returns legacy System Property
public String spOld() {
return spOld;
}
public int defaultValue() {
return defaultValue;
}
public boolean isSupported(Processor p) {
return processor == p;
}
int secureValue() {
return secureValue;
}
public ValueMapper mapper() {
return mapper;
}
}
/**
* Supported processors
*/
public static enum Processor {
ANY,
PARSER,
XPATH,
}
private static final int NO_LIMIT = 0;
/**
* Values of the properties
*/
private int[] values;
/**
* States of the settings for each property
*/
private State[] states;
/**
* Flag indicating if secure processing is set
*/
boolean secureProcessing;
/**
* Flag indicating the secure processing is set explicitly through factories'
* setFeature method and then the setSecureProcessing method
*/
boolean secureProcessingSet;
/**
* States that determine if properties are set explicitly
*/
private boolean[] isSet;
/**
* Index of the special entityCountInfo property
*/
private final int indexEntityCountInfo = 10000;
private String printEntityCountInfo = "";
/**
* Default constructor. Establishes default values for known security
* vulnerabilities.
*/
public XMLSecurityManager() {
this(false);
}
/**
* Instantiate Security Manager in accordance with the status of
* secure processing
* @param secureProcessing
*/
public XMLSecurityManager(boolean secureProcessing) {
values = new int[Limit.values().length];
states = new State[Limit.values().length];
isSet = new boolean[Limit.values().length];
this.secureProcessing = secureProcessing;
for (Limit limit : Limit.values()) {
if (secureProcessing) {
values[limit.ordinal()] = limit.secureValue;
states[limit.ordinal()] = State.FSP;
} else {
values[limit.ordinal()] = limit.defaultValue();
states[limit.ordinal()] = State.DEFAULT;
}
}
}
/**
* Returns a copy of the XMLSecurityManager.
* @return a copy of the XMLSecurityManager
*/
public XMLSecurityManager clone() {
try {
XMLSecurityManager copy = (XMLSecurityManager) super.clone();
copy.values = this.values.clone();
copy.states = this.states.clone();
copy.isSet = this.isSet.clone();
return copy;
} catch (CloneNotSupportedException e) {
// shouldn't happen as this class is Cloneable
throw new InternalError(e);
}
}
/**
* Returns a copy of the XMLSecurityManager that is updated with the
* current System Properties.
* @return a copy of the XMLSecurityManager
*/
public XMLSecurityManager cloneAndUpdate() {
XMLSecurityManager copy = clone();
copy.readSystemProperties();
return copy;
}
/**
* Returns the JDKCatalogResolver with the current setting of the RESOLVE
* property.
*
* @return the JDKCatalogResolver
*/
public CatalogResolver getJDKCatalogResolver() {
String resolve = getLimitValueAsString(Limit.JDKCATALOG_RESOLVE);
return CatalogManager.catalogResolver(
JdkXmlConfig.getInstance(false).getJdkCatalog(), toActionType(resolve));
}
// convert the string value of the RESOLVE property to the corresponding
// action type
private NotFoundAction toActionType(String resolve) {
for (NotFoundAction type : NotFoundAction.values()) {
if (type.toString().equals(resolve)) {
return type;
}
}
return null;
}
/**
* Setting FEATURE_SECURE_PROCESSING explicitly
*/
public void setSecureProcessing(boolean secure) {
secureProcessingSet = true;
secureProcessing = secure;
for (Limit limit : Limit.values()) {
if (secure) {
setLimit(limit.ordinal(), State.FSP, limit.secureValue());
} else {
setLimit(limit.ordinal(), State.FSP, limit.defaultValue());
}
}
}
/**
* Return the state of secure processing
* @return the state of secure processing
*/
public boolean isSecureProcessing() {
return secureProcessing;
}
/**
* Returns the state indicating whether the Secure Processing is set explicitly,
* via factories' setFeature and then this class' setSecureProcessing method.
* @return the state indicating whether the Secure Processing is set explicitly
*/
public boolean isSecureProcessingSet() {
return secureProcessingSet;
}
/**
* Finds a limit's new name with the given property name.
* @param propertyName the property name specified
* @return the limit's new name if found, null otherwise
*/
public String find(String propertyName) {
for (Limit limit : Limit.values()) {
if (limit.is(propertyName)) {
// current spec: new property name == systemProperty
return (limit.systemProperty != null) ? limit.systemProperty : limit.apiProperty;
}
}
//ENTITYCOUNT's new name is qName
if (ImplPropMap.ENTITYCOUNT.is(propertyName)) {
return ImplPropMap.ENTITYCOUNT.qName();
}
return null;
}
/**
* Set limit by property name and state
* @param propertyName property name
* @param state the state of the property
* @param value the value of the property
* @return true if the property is managed by the security manager; false
* if otherwise.
*/
public boolean setLimit(String propertyName, State state, Object value) {
// special property to return entity count info
if (ImplPropMap.ENTITYCOUNT.is(propertyName)) {
printEntityCountInfo = (String)value;
return true;
}
Limit limit = getEnumValue(propertyName);
if (limit != null) {
State pState = state;
if (state == State.APIPROPERTY) {
// ordinal is the index of the value array
pState = (Limit.values()[limit.ordinal()]).getState(propertyName);
}
setLimit(limit, pState, value);
return true;
}
return false;
}
/**
* Set the value for a specific limit.
*
* @param limit the limit
* @param state the state of the property
* @param value the value of the property
*/
public void setLimit(Limit limit, State state, int value) {
setLimit(limit.ordinal(), state, value);
}
/**
* Sets the value of a property by its enum name
*
* @param limit the limit
* @param state the state of the property
* @param value the value of the property
*/
public void setLimit(Limit limit, State state, Object value) {
int intValue = limit.mapper().toInt(value);
if (intValue < 0) {
intValue = 0;
}
setLimit(limit.ordinal(), state, intValue);
}
/**
* Set the value of a property by its index
*
* @param index the index of the property
* @param state the state of the property
* @param value the value of the property
*/
public void setLimit(int index, State state, int value) {
if (index == indexEntityCountInfo) {
//if it's explicitly set, it's treated as yes no matter the value
printEntityCountInfo = JdkConstants.JDK_YES;
} else {
//only update if it shall override
if (state.compareTo(states[index]) >= 0) {
values[index] = value;
states[index] = state;
isSet[index] = true;
}
}
}
/**
* Return the value of the specified property
*
* @param propertyName the property name
* @return the value of the property as a string. If a property is managed
* by this manager, its value shall not be null.
*/
public String getLimitAsString(String propertyName) {
int index = getIndex(propertyName);
if (index > -1) {
return getLimitValueByIndex(index);
}
return null;
}
/**
* Return the value of the specified property
*
* @param limit the property
* @return the value of the property
*/
public int getLimit(Limit limit) {
return values[limit.ordinal()];
}
/**
* Return the value of a property by its ordinal
*
* @param limit the property
* @return value of a property
*/
public String getLimitValueAsString(Limit limit) {
return limit.mapper().toString(values[limit.ordinal()]);
}
/**
* Return the value of a property by its ordinal
*
* @param index the index of a property
* @return limit of a property as a string
*/
public String getLimitValueByIndex(int index) {
if (index == indexEntityCountInfo) {
return printEntityCountInfo;
}
Limit limit = Limit.values()[index];
return limit.mapper().toString(values[index]);
}
/**
* Return the state of the limit property
*
* @param limit the limit
* @return the state of the limit property
*/
public State getState(Limit limit) {
return states[limit.ordinal()];
}
/**
* Return the state of the limit property
*
* @param limit the limit
* @return the state of the limit property
*/
public String getStateLiteral(Limit limit) {
return states[limit.ordinal()].literal();
}
/**
* Returns the enum value by its property name.
*
* @param propertyName property name
* @return the enum value if found; null otherwise
*/
public Limit getEnumValue(String propertyName) {
for (Limit limit : Limit.values()) {
if (limit.is(propertyName)) {
return limit;
}
}
return null;
}
/**
* Get the index by property name
*
* @param propertyName property name
* @return the index of the property if found; return -1 if not
*/
public int getIndex(String propertyName) {
for (Limit limit : Limit.values()) {
// see JDK-8265248, accept both the URL and jdk.xml as prefix
if (limit.is(propertyName)) {
//internally, ordinal is used as index
return limit.ordinal();
}
}
//special property to return entity count info
if (ImplPropMap.ENTITYCOUNT.is(propertyName)) {
return indexEntityCountInfo;
}
return -1;
}
/**
* Check if there's no limit defined by the Security Manager
* @param limit
* @return
*/
public boolean isNoLimit(int limit) {
return limit == NO_LIMIT;
}
/**
* Check if the size (length or count) of the specified limit property is
* over the limit
*
* @param limit the type of the limit property
* @param entityName the name of the entity
* @param size the size (count or length) of the entity
* @return true if the size is over the limit, false otherwise
*/
public boolean isOverLimit(Limit limit, String entityName, int size,
XMLLimitAnalyzer limitAnalyzer) {
return isOverLimit(limit.ordinal(), entityName, size, limitAnalyzer);
}
/**
* Check if the value (length or count) of the specified limit property is
* over the limit
*
* @param index the index of the limit property
* @param entityName the name of the entity
* @param size the size (count or length) of the entity
* @return true if the size is over the limit, false otherwise
*/
public boolean isOverLimit(int index, String entityName, int size,
XMLLimitAnalyzer limitAnalyzer) {
if (values[index] == NO_LIMIT) {
return false;
}
if (size > values[index]) {
limitAnalyzer.addValue(index, entityName, size);
return true;
}
return false;
}
/**
* Check against cumulated value
*
* @param limit the type of the limit property
* @param size the size (count or length) of the entity
* @return true if the size is over the limit, false otherwise
*/
public boolean isOverLimit(Limit limit, XMLLimitAnalyzer limitAnalyzer) {
return isOverLimit(limit.ordinal(), limitAnalyzer);
}
public boolean isOverLimit(int index, XMLLimitAnalyzer limitAnalyzer) {
if (values[index] == NO_LIMIT) {
return false;
}
if (index == Limit.ELEMENT_ATTRIBUTE_LIMIT.ordinal() ||
index == Limit.ENTITY_EXPANSION_LIMIT.ordinal() ||
index == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal() ||
index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal() ||
index == Limit.MAX_ELEMENT_DEPTH_LIMIT.ordinal() ||
index == Limit.MAX_NAME_LIMIT.ordinal()
) {
return (limitAnalyzer.getTotalValue(index) > values[index]);
} else {
return (limitAnalyzer.getValue(index) > values[index]);
}
}
public void debugPrint(XMLLimitAnalyzer limitAnalyzer) {
if (printEntityCountInfo.equals(JdkConstants.JDK_YES)) {
limitAnalyzer.debugPrint(this);
}
}
/**
* Indicate if a property is set explicitly
* @param limit the limit
* @return true if the limit is set, false otherwise
*/
public boolean isSet(Limit limit) {
return isSet[limit.ordinal()];
}
/**
* Checks whether the specified {@link Limit} is set and the value is
* as specified.
*
* @param limit the {@link Limit}
* @param value the value
* @return true if the {@code Limit} is set and the values match
*/
public boolean is(Limit limit, int value) {
return getLimit(limit) == value;
}
/**
* Checks whether the specified {@link Limit} is set and the value is
* 1 (true for a property of boolean type).
*
* @param limit the {@link Limit}
*
* @return true if the {@code Limit} is set and the value is 1
*/
public boolean is(Limit limit) {
return getLimit(limit) == 1;
}
public boolean printEntityCountInfo() {
return printEntityCountInfo.equals(JdkConstants.JDK_YES);
}
/**
* Read system properties, or the configuration file
*/
public void readSystemProperties() {
for (Limit limit : Limit.values()) {
if (State.SYSTEMPROPERTY.compareTo(states[limit.ordinal()]) >= 0 &&
limit.systemProperty() != null) {
// attempts to read both the current and old system propery
if (!getSystemProperty(limit, limit.systemProperty())
&& (!getSystemProperty(limit, limit.spOld()))) {
//if system property is not found, try the config file
if (State.JAXPDOTPROPERTIES.compareTo(states[limit.ordinal()]) >= 0) {
getPropertyConfig(limit, limit.systemProperty());
}
}
}
}
}
// Array list to store printed warnings for each SAX parser used
private static final CopyOnWriteArrayList<String> printedWarnings = new CopyOnWriteArrayList<>();
/**
* Prints out warnings if a parser does not support the specified feature/property.
*
* @param parserClassName the name of the parser class
* @param propertyName the property name
* @param exception the exception thrown by the parser
*/
public static void printWarning(String parserClassName, String propertyName, SAXException exception) {
String key = parserClassName+":"+propertyName;
if (printedWarnings.addIfAbsent(key)) {
System.err.println( "Warning: "+parserClassName+": "+exception.getMessage());
}
}
/**
* Reads a system property, sets value and state if found.
*
* @param limit the limit property
* @param sysPropertyName the name of system property
*/
private boolean getSystemProperty(Limit limit, String sysPropertyName) {
if (sysPropertyName == null) return false;
try {
String value = System.getProperty(sysPropertyName);
if (value != null && !value.equals("")) {
setLimit(limit, State.SYSTEMPROPERTY, value);
return true;
}
} catch (NumberFormatException e) {
//invalid setting
throw new NumberFormatException("Invalid setting for system property: " + limit.systemProperty());
}
return false;
}
/**
* Reads a property from a configuration file, if any.
*
* @param limit the limit property
* @param sysPropertyName the name of system property
* @return
*/
private boolean getPropertyConfig(Limit limit, String sysPropertyName) {
try {
String value = SecuritySupport.readConfig(sysPropertyName);
if (value != null && !value.equals("")) {
setLimit(limit, State.JAXPDOTPROPERTIES, value);
return true;
}
} catch (NumberFormatException e) {
//invalid setting
throw new NumberFormatException("Invalid setting for system property: " + limit.systemProperty());
}
return false;
}
/**
* Convert a value set through setProperty to XMLSecurityManager.
* If the value is an instance of XMLSecurityManager, use it to override the default;
* If the value is an old SecurityManager, convert to the new XMLSecurityManager.
*
* @param value user specified security manager
* @param securityManager an instance of XMLSecurityManager
* @return an instance of the new security manager XMLSecurityManager
*/
public static XMLSecurityManager convert(Object value, XMLSecurityManager securityManager) {
if (value == null) {
if (securityManager == null) {
securityManager = new XMLSecurityManager(true);
}
return securityManager;
}
if (value instanceof XMLSecurityManager) {
return (XMLSecurityManager)value;
} else {
if (securityManager == null) {
securityManager = new XMLSecurityManager(true);
}
if (value instanceof SecurityManager) {
SecurityManager origSM = (SecurityManager)value;
securityManager.setLimit(Limit.MAX_OCCUR_NODE_LIMIT, State.APIPROPERTY, origSM.getMaxOccurNodeLimit());
securityManager.setLimit(Limit.ENTITY_EXPANSION_LIMIT, State.APIPROPERTY, origSM.getEntityExpansionLimit());
securityManager.setLimit(Limit.ELEMENT_ATTRIBUTE_LIMIT, State.APIPROPERTY, origSM.getElementAttrLimit());
}
return securityManager;
}
}
/**
* Represents a mapper for properties of type String. The input is expected
* to be a String or Object. If there is a map, the mappings are between the
* keys and values within the map.
*/
public static class StringMapper extends ValueMapper {
private final Map<String, Integer> map;
private final Map<Integer, String> reverseMap;
public StringMapper(Map<String, Integer> map) {
this.map = map;
if (map != null) {
reverseMap = map.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
} else {
reverseMap = null;
}
}
/**
* Finds the mapping int value with the specified property value. This
* method will try to convert the provided value to an integer if no
* mapping is found.
* @param value the property value
* @return the mapping int value if found, null otherwise
*/
@Override
public int toInt(Object value) {
Objects.requireNonNull(value);
Integer iVal;
if (map != null) {
iVal = map.get(((String)value).toLowerCase());
iVal = (iVal == null) ? 0 : iVal;
} else {
try {
iVal = (int)Double.parseDouble((String)value);
} catch (NumberFormatException e) {
// Note: this is the currently expected behavior. It may be
// appropriate for the setter to catch it.
throw new NumberFormatException("Invalid setting " + value
+ " for a property of Integer type.");
}
}
return iVal;
}
@Override
public String toObject(int value) {
if (reverseMap != null) {
return reverseMap.get(value);
}
return Integer.toString(value);
}
@Override
public String toString(int value) {
return toObject(value);
}
}
/**
* Represents a mapper for properties of type Integer. The input is expected
* to be either an Integer or String.
*/
public static class IntegerMapper extends ValueMapper {
@Override
public int toInt(Object value) {
Objects.requireNonNull(value);
Integer iVal;
if (value instanceof Integer) {
iVal = (Integer)value;
} else {
try {
iVal = Integer.parseInt((String)value);
} catch (NumberFormatException e) {
// Note: this is the currently expected behavior. It may be
// appropriate for the setter to catch it.
throw new NumberFormatException("Invalid setting " + value
+ " for a property of Integer type.");
}
}
return iVal;
}
@Override
public Integer toObject(int value) {
return value;
}
@Override
public String toString(int value) {
return Integer.toString(value);
}
}
/**
* Represents a mapper for properties of type Boolean. The input is expected
* to be either a Boolean or String.
*/
public static class BooleanMapper extends ValueMapper {
@Override
public int toInt(Object value) {
Objects.requireNonNull(value);
Boolean bVal;
if (value instanceof Boolean) {
bVal = (Boolean)value;
} else {
bVal = ((String)value).equalsIgnoreCase("true");
}
return bVal ? 1 : 0;
}
@Override
public Boolean toObject(int value) {
return value != 0;
}
@Override
public String toString(int value) {
return Boolean.toString(value != 0);
}
}
/**
* Represents a mapper of property values between int and other types, such as
* Boolean, String, and Object.
*/
public static abstract class ValueMapper {
// converts to an int value from that of the specified type
public abstract int toInt(Object value);
// converts the int value back to the original type
public abstract Object toObject(int value);
// converts the int value of a property to a String representation
public abstract String toString(int value);
}
/**
* Represents a mapper of property values between int and other types, such as
* Boolean, String, and Object.
*
* @param <T> the value type to be mapped with an int value
*/
public abstract class ValueMapper1<T> {
// converts to an int value from that of the specified type
public abstract int toInt(T value);
// converts the int value back to the original type
public abstract T toObject(int value);
// converts the int value of a property to a String representation
public abstract String toString(int value);
}
}