From 5737bd52901f674b28fdfa7687b52185a177a64e Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 5 Dec 2017 01:16:38 +0100 Subject: [PATCH 1/4] MID-4210: Localization of value policy checking messages --- .../gui/api/util/WebComponentUtil.java | 42 +- .../common/LocalizationServiceImpl.java | 34 +- .../schema/MidpointParsingMigrator.java | 3 +- .../schema/result/OperationResult.java | 6 +- .../schema/result/OperationResultFactory.java | 4 +- .../schema/util/LocalizationUtil.java | 68 ++- .../resources/localization/schema.properties | 19 + .../xml/ns/public/common/common-core-3.xsd | 164 +++++-- .../midpoint/util/LocalizableMessage.java | 107 +---- .../util/LocalizableMessageBuilder.java | 15 +- .../midpoint/util/LocalizableMessageList.java | 91 ++++ .../util/LocalizableMessageListBuilder.java | 65 +++ .../util/SingleLocalizableMessage.java | 132 ++++++ .../ObjectValuePolicyEvaluator.java | 91 ++-- .../stringpolicy/StringPolicyUtils.java | 8 +- .../stringpolicy/ValuePolicyProcessor.java | 434 ++++++++---------- .../ModelInteractionServiceImpl.java | 2 +- .../impl/expr/MidpointFunctionsImpl.java | 4 +- .../midpoint/model/impl/lens/LensUtil.java | 6 +- .../CredentialPolicyEvaluator.java | 26 +- .../credentials/NoncePolicyEvaluator.java | 5 + .../credentials/PasswordPolicyEvaluator.java | 5 + .../ProjectionCredentialsProcessor.java | 17 +- .../SecurityQuestionsPolicyEvaluator.java | 8 +- .../evaluators/ConstraintEvaluatorHelper.java | 22 +- .../test/AbstractModelIntegrationTest.java | 14 +- .../policy/AssignmentPolicyAspectPart.java | 3 +- .../policy/ObjectPolicyAspectPart.java | 2 +- .../primary/policy/PolicyRuleBasedAspect.java | 13 +- 29 files changed, 866 insertions(+), 544 deletions(-) create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageList.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java create mode 100644 infra/util/src/main/java/com/evolveum/midpoint/util/SingleLocalizableMessage.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java index 51a35072106..9d260e4302e 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java @@ -90,12 +90,10 @@ import org.joda.time.format.DateTimeFormat; import com.evolveum.midpoint.gui.api.GuiStyleConstants; -import com.evolveum.midpoint.gui.api.SubscriptionType; import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.model.NonEmptyModel; import com.evolveum.midpoint.gui.api.page.PageBase; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.prism.Containerable; import com.evolveum.midpoint.prism.Objectable; import com.evolveum.midpoint.prism.PrismContainer; @@ -128,9 +126,7 @@ import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.DeltaConvertor; -import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SchemaConstantsGenerated; -import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; @@ -152,7 +148,6 @@ import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; import com.evolveum.midpoint.web.component.input.DisplayableValueChoiceRenderer; import com.evolveum.midpoint.web.component.input.DropDownChoicePanel; -import com.evolveum.midpoint.web.component.prism.InputPanel; import com.evolveum.midpoint.web.component.util.Selectable; import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; @@ -178,7 +173,6 @@ import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; import com.evolveum.midpoint.web.util.DateValidator; import com.evolveum.midpoint.web.util.InfoTooltipBehavior; -import com.evolveum.midpoint.web.util.ObjectTypeGuiDescriptor; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType; @@ -346,10 +340,22 @@ public static String resolveLocalizableMessage(LocalizableMessageType localizabl if (localizableMessage == null) { return null; } - return resolveLocalizableMessage(LocalizationUtil.parseLocalizableMessageType(localizableMessage), component); + return resolveLocalizableMessage(LocalizationUtil.toLocalizableMessage(localizableMessage), component); } public static String resolveLocalizableMessage(LocalizableMessage localizableMessage, Component component) { + if (localizableMessage == null) { + return null; + } else if (localizableMessage instanceof SingleLocalizableMessage) { + return resolveLocalizableMessage((SingleLocalizableMessage) localizableMessage, component); + } else if (localizableMessage instanceof LocalizableMessageList) { + return resolveLocalizableMessage((LocalizableMessageList) localizableMessage, component); + } else { + throw new AssertionError("Unsupported localizable message type: " + localizableMessage); + } + } + + private static String resolveLocalizableMessage(SingleLocalizableMessage localizableMessage, Component component) { if (localizableMessage == null) { return null; } @@ -360,7 +366,11 @@ public static String resolveLocalizableMessage(LocalizableMessage localizableMes break; // the key exists => we can use the current localizableMessage } } - localizableMessage = localizableMessage.getFallbackLocalizableMessage(); + if (localizableMessage.getFallbackLocalizableMessage() instanceof SingleLocalizableMessage) { + localizableMessage = (SingleLocalizableMessage) localizableMessage.getFallbackLocalizableMessage(); + } else { + return resolveLocalizableMessage(localizableMessage.getFallbackLocalizableMessage(), component); + } } String key = localizableMessage.getKey() != null ? localizableMessage.getKey() : localizableMessage.getFallbackMessage(); StringResourceModel stringResourceModel = new StringResourceModel(key, component) @@ -372,8 +382,22 @@ public static String resolveLocalizableMessage(LocalizableMessage localizableMes return rv; } + // todo deduplicate with similar method in LocalizationServiceImpl + private static String resolveLocalizableMessage(LocalizableMessageList msgList, Component component) { + String separator = resolveIfPresent(msgList.getSeparator(), component); + String prefix = resolveIfPresent(msgList.getPrefix(), component); + String suffix = resolveIfPresent(msgList.getPostfix(), component); + return msgList.getMessages().stream() + .map(m -> resolveLocalizableMessage(m, component)) + .collect(Collectors.joining(separator, prefix, suffix)); + } + + private static String resolveIfPresent(LocalizableMessage msg, Component component) { + return msg != null ? resolveLocalizableMessage(msg, component) : ""; + } + private static Object[] resolveArguments(Object[] args, Component component) { - if (args == null){ + if (args == null) { return null; } Object[] rv = new Object[args.length]; diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java b/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java index 66e2b753285..3e2076b1168 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java @@ -18,6 +18,8 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.LocalizableMessageList; +import com.evolveum.midpoint.util.SingleLocalizableMessage; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -34,6 +36,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; /** * Created by Viliam Repan (lazyman). @@ -78,7 +81,7 @@ public String translate(String key, Object[] params, Locale locale, String defau String value = source.getMessage(key, translated, locale); if (StringUtils.isNotEmpty(value)) { if (LOG.isTraceEnabled()) { - LOG.trace("Resolved key {} to value {} using message source {}", new Object[]{key, value, source}); + LOG.trace("Resolved key {} to value {} using message source {}", key, value, source); } return value; @@ -93,19 +96,42 @@ public String translate(String key, Object[] params, Locale locale, String defau @Override public String translate(LocalizableMessage msg, Locale locale) { + if (msg == null) { + return null; + } else if (msg instanceof SingleLocalizableMessage) { + return translate((SingleLocalizableMessage) msg, locale); + } else if (msg instanceof LocalizableMessageList) { + return translate((LocalizableMessageList) msg, locale); + } else { + throw new AssertionError("Unsupported localizable message type: " + msg); + } + } + + // todo deduplicate with similar method in WebComponentUtil + public String translate(LocalizableMessageList msgList, Locale locale) { + String separator = translateIfPresent(msgList.getSeparator(), locale); + String prefix = translateIfPresent(msgList.getPrefix(), locale); + String suffix = translateIfPresent(msgList.getPostfix(), locale); + return msgList.getMessages().stream() + .map(m -> translate(m, locale)) + .collect(Collectors.joining(separator, prefix, suffix)); + } + + private String translateIfPresent(LocalizableMessage msg, Locale locale) { + return msg != null ? translate(msg, locale) : ""; + } + + public String translate(SingleLocalizableMessage msg, Locale locale) { String translated = translate(msg.getKey(), msg.getArgs(), locale); if (StringUtils.isNotEmpty(translated)) { return translated; } - if (msg.getFallbackLocalizableMessage() != null) { translated = translate(msg.getFallbackLocalizableMessage(), locale); - if (StringUtils.isNotEmpty(translated)) { return translated; } } - return msg.getFallbackMessage(); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidpointParsingMigrator.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidpointParsingMigrator.java index 900af620ca1..5e1936f8991 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidpointParsingMigrator.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/MidpointParsingMigrator.java @@ -20,6 +20,7 @@ import com.evolveum.midpoint.prism.marshaller.ParsingMigrator; import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SingleLocalizableMessageType; /** * @author mederly @@ -30,7 +31,7 @@ public class MidpointParsingMigrator implements ParsingMigrator { public T tryParsingPrimitiveAsBean(PrimitiveXNode primitive, Class beanClass, ParsingContext pc) { if (LocalizableMessageType.class.equals(beanClass)) { //noinspection unchecked - return (T) new LocalizableMessageType().fallbackMessage(primitive.getStringValue()); + return (T) new SingleLocalizableMessageType().fallbackMessage(primitive.getStringValue()); } else { return null; } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java index b7e19d671d9..5e59adb980a 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java @@ -144,6 +144,10 @@ public OperationResult(String operation, String messageCode, String message) { this(operation, null, OperationResultStatus.SUCCESS, 0, messageCode, message, null, null, null); } + public OperationResult(String operation, OperationResultStatus status, LocalizableMessage userFriendlyMessage) { + this(operation, null, status, 0, null, null, userFriendlyMessage, null, null); + } + public OperationResult(String operation, long token, String messageCode, String message) { this(operation, null, OperationResultStatus.SUCCESS, token, messageCode, message, null, null, null); } @@ -1240,7 +1244,7 @@ public static OperationResult createOperationResult(OperationResultType result) LocalizableMessage localizableMessage = null; LocalizableMessageType message = result.getUserFriendlyMessage(); if (message != null) { - localizableMessage = LocalizationUtil.parseLocalizableMessageType(message); + localizableMessage = LocalizationUtil.toLocalizableMessage(message); } OperationResult opResult = new OperationResult(result.getOperation(), params, context, returns, diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResultFactory.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResultFactory.java index 45c45b509d1..4bd3f2e6c47 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResultFactory.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResultFactory.java @@ -73,7 +73,7 @@ public static OperationResultType createOperationResult(String operation, } ObjectFactory factory = new ObjectFactory(); - LocalizableMessageType localizedMessageType = factory.createLocalizableMessageType(); + SingleLocalizableMessageType localizedMessageType = factory.createSingleLocalizableMessageType(); result.setUserFriendlyMessage(localizedMessageType); localizedMessageType.setKey(localizedMessage); if (localizedArguments == null || localizedArguments.length == 0) { @@ -125,7 +125,7 @@ public static OperationResultType createOperationResult(String operation, for (Entry entry : set) { entryType = factory.createEntryType(); entryType.setKey(entry.getKey()); - entryType.setEntryValue(new JAXBElement(EntryType.F_ENTRY_VALUE, Element.class, entry.getValue())); + entryType.setEntryValue(new JAXBElement<>(EntryType.F_ENTRY_VALUE, Element.class, entry.getValue())); paramsType.getEntry().add(entryType); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/LocalizationUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/LocalizationUtil.java index 06d100a44e1..d6a04441ddf 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/LocalizationUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/LocalizationUtil.java @@ -17,10 +17,11 @@ package com.evolveum.midpoint.schema.util; import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.LocalizableMessageBuilder; +import com.evolveum.midpoint.util.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageArgumentType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageListType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SingleLocalizableMessageType; import org.jetbrains.annotations.NotNull; import java.text.MessageFormat; @@ -55,7 +56,30 @@ public static String resolve(String key, Object... params ) { } public static LocalizableMessageType createLocalizableMessageType(LocalizableMessage message) { - LocalizableMessageType rv = new LocalizableMessageType(); + if (message == null) { + return null; + } else if (message instanceof SingleLocalizableMessage) { + return createLocalizableMessageType((SingleLocalizableMessage) message); + } else if (message instanceof LocalizableMessageList) { + return createLocalizableMessageType((LocalizableMessageList) message); + } else { + throw new AssertionError("Unsupported localizable message type: " + message); + } + } + + @NotNull + private static LocalizableMessageListType createLocalizableMessageType(@NotNull LocalizableMessageList messageList) { + LocalizableMessageListType rv = new LocalizableMessageListType(); + messageList.getMessages().forEach(message -> rv.getMessage().add(createLocalizableMessageType(message))); + rv.setSeparator(createLocalizableMessageType(messageList.getSeparator())); + rv.setPrefix(createLocalizableMessageType(messageList.getPrefix())); + rv.setPostfix(createLocalizableMessageType(messageList.getPostfix())); + return rv; + } + + @NotNull + private static SingleLocalizableMessageType createLocalizableMessageType(@NotNull SingleLocalizableMessage message) { + SingleLocalizableMessageType rv = new SingleLocalizableMessageType(); rv.setKey(message.getKey()); if (message.getArgs() != null) { for (Object argument : message.getArgs()) { @@ -77,23 +101,29 @@ public static LocalizableMessageType createLocalizableMessageType(LocalizableMes } public static LocalizableMessageType createForFallbackMessage(String fallbackMessage) { - return new LocalizableMessageType().fallbackMessage(fallbackMessage); + return new SingleLocalizableMessageType().fallbackMessage(fallbackMessage); } public static LocalizableMessageType createForKey(String key) { - return new LocalizableMessageType().key(key); + return new SingleLocalizableMessageType().key(key); } - public static LocalizableMessage parseLocalizableMessageType(@NotNull LocalizableMessageType message) { - return parseLocalizableMessageType(message, null); + public static LocalizableMessage toLocalizableMessage(@NotNull LocalizableMessageType message) { + if (message instanceof SingleLocalizableMessageType) { + return toLocalizableMessage((SingleLocalizableMessageType) message); + } else if (message instanceof LocalizableMessageListType) { + return toLocalizableMessage((LocalizableMessageListType) message); + } else { + throw new AssertionError("Unknown LocalizableMessageType type: " + message); + } } - public static LocalizableMessage parseLocalizableMessageType(@NotNull LocalizableMessageType message, LocalizableMessage defaultMessage) { + public static LocalizableMessage toLocalizableMessage(@NotNull SingleLocalizableMessageType message) { LocalizableMessage fallbackLocalizableMessage; if (message.getFallbackLocalizableMessage() != null) { - fallbackLocalizableMessage = parseLocalizableMessageType(message.getFallbackLocalizableMessage(), defaultMessage); + fallbackLocalizableMessage = toLocalizableMessage(message.getFallbackLocalizableMessage()); } else { - fallbackLocalizableMessage = defaultMessage; + fallbackLocalizableMessage = null; } if (message.getKey() == null && message.getFallbackMessage() == null) { return fallbackLocalizableMessage; @@ -107,11 +137,21 @@ public static LocalizableMessage parseLocalizableMessageType(@NotNull Localizabl } } + private static LocalizableMessage toLocalizableMessage(@NotNull LocalizableMessageListType messageList) { + LocalizableMessageListBuilder builder = new LocalizableMessageListBuilder(); + messageList.getMessage().forEach(m -> builder.addMessage(toLocalizableMessage(m))); + return builder + .separator(toLocalizableMessage(messageList.getSeparator())) + .prefix(toLocalizableMessage(messageList.getPrefix())) + .postfix(toLocalizableMessage(messageList.getPostfix())) + .build(); + } + private static List convertLocalizableMessageArguments(List arguments) { List rv = new ArrayList<>(); for (LocalizableMessageArgumentType argument : arguments) { if (argument.getLocalizable() != null) { - rv.add(parseLocalizableMessageType(argument.getLocalizable(), null)); + rv.add(toLocalizableMessage(argument.getLocalizable())); } else { rv.add(argument.getValue()); // may be null } @@ -119,12 +159,8 @@ private static List convertLocalizableMessageArguments(List + xmlns:tns="http://midpoint.evolveum.com/xml/ns/public/common/common-3" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:c="http://midpoint.evolveum.com/xml/ns/public/common/common-3" + xmlns:a="http://prism.evolveum.com/xml/ns/public/annotation-3" + xmlns:t="http://prism.evolveum.com/xml/ns/public/types-3" + xmlns:q="http://prism.evolveum.com/xml/ns/public/query-3" + xmlns:icfs="http://midpoint.evolveum.com/xml/ns/public/connector/icf-1/resource-schema-3" + xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" + xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" + elementFormDefault="qualified" + jaxb:extensionBindingPrefixes="xjc" + jaxb:version="2.0"> @@ -11763,11 +11761,24 @@ + + + +

+ Localizable message that will be displayed to user when the expression check fails. + This one takes precedence over (non-localizable) failureMessage. +

+
+ + 3.7 + +
+

- Message thet will be displayed to user when the expression check fails. + Message that will be displayed to user when the expression check fails.

@@ -18155,45 +18166,106 @@ - + A message that is to be localized into specified language. + Abstract superclass for both single-item message and a message list. 3.7 - - - - - Localization key. - - - - - - - Arguments to be used as values for localized message parameters. - - - - - - - Fallback localizable message to be used when key couldn't be resolved. Mutually exclusive with fallbackMessage. - - - - - - - Fallback message to be used when key couldn't be resolved. Mutually exclusive with fallbackLocalizableMessage. - - - - + + + + + + A message that is to be localized into specified language. + + + 3.7 + + + + + + + + + Localization key. + + + + + + + Arguments to be used as values for localized message parameters. + + + + + + + Fallback localizable message to be used when key couldn't be resolved. Mutually exclusive with fallbackMessage. + + + + + + + Fallback message to be used when key couldn't be resolved. Mutually exclusive with fallbackLocalizableMessage. + + + + + + + + + + + + List of localizable messages, to be presented as a single message. + + + 3.7 + + + + + + + + + Messages. + + + + + + + Separator to be put between the messages. Typically a comma. + + + + + + + Prefix of the sequence. + + + + + + + Postfix of the sequence. + + + + + + diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessage.java b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessage.java index 0751d6e50e9..ea50a4aa0c8 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessage.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessage.java @@ -16,114 +16,19 @@ package com.evolveum.midpoint.util; import java.io.Serializable; -import java.util.Arrays; -import java.util.Objects; /** * @author semancik + * @author mederly * */ -public class LocalizableMessage implements Serializable, ShortDumpable { - private static final long serialVersionUID = 1L; +public interface LocalizableMessage extends Serializable, ShortDumpable { - final private String key; - final private Object[] args; - // at most one of the following can be present - final private LocalizableMessage fallbackLocalizableMessage; - final private String fallbackMessage; + String getFallbackMessage(); - public LocalizableMessage(String key, Object[] args, LocalizableMessage fallbackLocalizableMessage) { - super(); - this.key = key; - this.args = args; - this.fallbackLocalizableMessage = fallbackLocalizableMessage; - this.fallbackMessage = null; - } - - public LocalizableMessage(String key, Object[] args, String fallbackMessage) { - super(); - this.key = key; - this.args = args; - this.fallbackLocalizableMessage = null; - this.fallbackMessage = fallbackMessage; - } - - /** - * Message key. This is the key in localization files that - * determine message or message template. - */ - public String getKey() { - return key; - } - - /** - * Message template arguments. - */ - public Object[] getArgs() { - return args; - } - - /** - * Fallback message. This message is used in case that the - * message key cannot be found in the localization files. - */ - public String getFallbackMessage() { - return fallbackMessage; - } + boolean isEmpty(); - /** - * Fallback localization message. This message is used in case that the - * message key cannot be found in the localization files. - */ - public LocalizableMessage getFallbackLocalizableMessage() { - return fallbackLocalizableMessage; + static boolean isEmpty(LocalizableMessage msg) { + return msg == null || msg.isEmpty(); } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof LocalizableMessage)) - return false; - LocalizableMessage that = (LocalizableMessage) o; - return Objects.equals(key, that.key) && - Arrays.equals(args, that.args) && - Objects.equals(fallbackLocalizableMessage, that.fallbackLocalizableMessage) && - Objects.equals(fallbackMessage, that.fallbackMessage); - } - - @Override - public int hashCode() { - return Objects.hash(key, args, fallbackLocalizableMessage, fallbackMessage); - } - - @Override - public String toString() { - return "LocalizableMessage(" + key + ": " + Arrays.toString(args) + " (" - + (fallbackMessage != null ? fallbackMessage : fallbackLocalizableMessage) + "))"; - } - - @Override - public void shortDump(StringBuilder sb) { - if (key != null) { - sb.append(key); - if (args != null) { - sb.append(": "); - sb.append(Arrays.toString(args)); - } - if (fallbackMessage != null) { - sb.append(" ("); - sb.append(fallbackMessage); - sb.append(")"); - } - if (fallbackLocalizableMessage != null) { - sb.append(" ("); - sb.append(fallbackLocalizableMessage.shortDump()); - sb.append(")"); - } - } else { - sb.append(fallbackLocalizableMessage != null ? fallbackLocalizableMessage.shortDump() : fallbackMessage); - } - } - } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageBuilder.java b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageBuilder.java index 390b84113a0..07d0e39a843 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageBuilder.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageBuilder.java @@ -31,7 +31,6 @@ public class LocalizableMessageBuilder { private LocalizableMessage fallbackLocalizableMessage; public LocalizableMessageBuilder() { - super(); } public LocalizableMessageBuilder key(String key) { @@ -39,8 +38,8 @@ public LocalizableMessageBuilder key(String key) { return this; } - public static LocalizableMessage buildKey(String key) { - return new LocalizableMessage(key, null, (LocalizableMessage) null); + public static SingleLocalizableMessage buildKey(String key) { + return new SingleLocalizableMessage(key, null, (SingleLocalizableMessage) null); } public LocalizableMessageBuilder args(Object... args) { @@ -68,18 +67,18 @@ public LocalizableMessageBuilder fallbackLocalizableMessage(LocalizableMessage f return this; } - public static LocalizableMessage buildFallbackMessage(String fallbackMessage) { - return new LocalizableMessage(null, null, fallbackMessage); + public static SingleLocalizableMessage buildFallbackMessage(String fallbackMessage) { + return new SingleLocalizableMessage(null, null, fallbackMessage); } - public LocalizableMessage build() { + public SingleLocalizableMessage build() { if (fallbackMessage != null) { if (fallbackLocalizableMessage != null) { throw new IllegalStateException("fallbackMessage and fallbackLocalizableMessage cannot be both set"); } - return new LocalizableMessage(key, args.toArray(), fallbackMessage); + return new SingleLocalizableMessage(key, args.toArray(), fallbackMessage); } else { - return new LocalizableMessage(key, args.toArray(), fallbackLocalizableMessage); + return new SingleLocalizableMessage(key, args.toArray(), fallbackLocalizableMessage); } } } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageList.java b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageList.java new file mode 100644 index 00000000000..3fb8d77c990 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageList.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.util; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author mederly + */ +public class LocalizableMessageList implements LocalizableMessage { + + public static final LocalizableMessage SPACE = LocalizableMessageBuilder.buildFallbackMessage(" "); + public static final LocalizableMessage COMMA = LocalizableMessageBuilder.buildFallbackMessage(", "); + public static final LocalizableMessage SEMICOLON = LocalizableMessageBuilder.buildFallbackMessage("; "); + + private final List messages; + private final LocalizableMessage separator; + private final LocalizableMessage prefix; + private final LocalizableMessage postfix; + + public LocalizableMessageList(List messages, LocalizableMessage separator, LocalizableMessage prefix, LocalizableMessage postfix) { + this.messages = messages; + this.separator = separator; + this.prefix = prefix; + this.postfix = postfix; + } + + public List getMessages() { + return messages; + } + + public LocalizableMessage getSeparator() { + return separator; + } + + public LocalizableMessage getPrefix() { + return prefix; + } + + public LocalizableMessage getPostfix() { + return postfix; + } + + @Override + public String getFallbackMessage() { + String msg = messages.stream() + .filter(m -> m.getFallbackMessage() != null) + .map(m -> m.getFallbackMessage()) + .collect(Collectors.joining("; ")); + if (!msg.isEmpty()) { + return msg; + } else { + return messages.size() + " message(s)"; + } + } + + @Override + public void shortDump(StringBuilder sb) { + boolean first = true; + for (LocalizableMessage message : messages) { + if (first) { + first = false; + } else { + sb.append("; "); + } + message.shortDump(sb); + } + } + + @Override + public boolean isEmpty() { + return LocalizableMessage.isEmpty(prefix) + && LocalizableMessage.isEmpty(postfix) + && messages.stream().allMatch(m -> m.isEmpty()); + } +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java new file mode 100644 index 00000000000..169173e6ac5 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author mederly + */ +public class LocalizableMessageListBuilder { + private List messages = new ArrayList<>(); + private LocalizableMessage separator; + private LocalizableMessage prefix; + private LocalizableMessage postfix; + + public LocalizableMessageListBuilder message(LocalizableMessage message) { + messages.add(message); + return this; + } + + public void addMessage(LocalizableMessage message) { + messages.add(message); + } + + public LocalizableMessageListBuilder messages(Collection messages) { + this.messages.addAll(messages); + return this; + } + + public LocalizableMessageListBuilder separator(LocalizableMessage value) { + separator = value; + return this; + } + + public LocalizableMessageListBuilder prefix(LocalizableMessage value) { + prefix = value; + return this; + } + + public LocalizableMessageListBuilder postfix(LocalizableMessage value) { + postfix = value; + return this; + } + + public LocalizableMessageList build() { + return new LocalizableMessageList(messages, separator, prefix, postfix); + } + +} diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/SingleLocalizableMessage.java b/infra/util/src/main/java/com/evolveum/midpoint/util/SingleLocalizableMessage.java new file mode 100644 index 00000000000..bc5aff10c73 --- /dev/null +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/SingleLocalizableMessage.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.util; + +import java.util.Arrays; +import java.util.Objects; + +/** + * @author semancik + * + */ +public class SingleLocalizableMessage implements LocalizableMessage { + private static final long serialVersionUID = 1L; + + final private String key; + final private Object[] args; + // at most one of the following can be present + final private LocalizableMessage fallbackLocalizableMessage; + final private String fallbackMessage; + + public SingleLocalizableMessage(String key, Object[] args, LocalizableMessage fallbackLocalizableMessage) { + super(); + this.key = key; + this.args = args; + this.fallbackLocalizableMessage = fallbackLocalizableMessage; + this.fallbackMessage = null; + } + + public SingleLocalizableMessage(String key, Object[] args, String fallbackMessage) { + super(); + this.key = key; + this.args = args; + this.fallbackLocalizableMessage = null; + this.fallbackMessage = fallbackMessage; + } + + /** + * Message key. This is the key in localization files that + * determine message or message template. + */ + public String getKey() { + return key; + } + + /** + * Message template arguments. + */ + public Object[] getArgs() { + return args; + } + + /** + * Fallback message. This message is used in case that the + * message key cannot be found in the localization files. + */ + public String getFallbackMessage() { + return fallbackMessage; + } + + /** + * Fallback localization message. This message is used in case that the + * message key cannot be found in the localization files. + */ + public LocalizableMessage getFallbackLocalizableMessage() { + return fallbackLocalizableMessage; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof SingleLocalizableMessage)) + return false; + SingleLocalizableMessage that = (SingleLocalizableMessage) o; + return Objects.equals(key, that.key) && + Arrays.equals(args, that.args) && + Objects.equals(fallbackLocalizableMessage, that.fallbackLocalizableMessage) && + Objects.equals(fallbackMessage, that.fallbackMessage); + } + + @Override + public int hashCode() { + return Objects.hash(key, args, fallbackLocalizableMessage, fallbackMessage); + } + + @Override + public String toString() { + return "LocalizableMessage(" + key + ": " + Arrays.toString(args) + " (" + + (fallbackMessage != null ? fallbackMessage : fallbackLocalizableMessage) + "))"; + } + + @Override + public void shortDump(StringBuilder sb) { + if (key != null) { + sb.append(key); + if (args != null) { + sb.append(": "); + sb.append(Arrays.toString(args)); + } + if (fallbackMessage != null) { + sb.append(" ("); + sb.append(fallbackMessage); + sb.append(")"); + } + if (fallbackLocalizableMessage != null) { + sb.append(" ("); + sb.append(fallbackLocalizableMessage.shortDump()); + sb.append(")"); + } + } else { + sb.append(fallbackLocalizableMessage != null ? fallbackLocalizableMessage.shortDump() : fallbackMessage); + } + } + + @Override + public boolean isEmpty() { + return key == null && LocalizableMessage.isEmpty(fallbackLocalizableMessage) && fallbackMessage == null; + } +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java index a2f6a75e838..194e212b819 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java @@ -16,7 +16,6 @@ package com.evolveum.midpoint.model.common.stringpolicy; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.xml.datatype.DatatypeConstants; @@ -24,7 +23,6 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; -import com.evolveum.midpoint.prism.Containerable; import com.evolveum.midpoint.prism.PrismContainer; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.crypto.EncryptionException; @@ -34,17 +32,18 @@ import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; -import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.LocalizableMessageBuilder; +import com.evolveum.midpoint.util.LocalizableMessageList; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; @@ -52,12 +51,9 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractCredentialType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialPolicyType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordCredentialsPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordHistoryEntryType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityPolicyType; @@ -193,21 +189,17 @@ public OperationResult validateProtectedStringValue(ProtectedStringType value) t public OperationResult validateStringValue(String clearValue) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { OperationResult result = new OperationResult(OPERATION_VALIDATE_VALUE); - // TODO: later we need to replace the string message with something more structured. - // something that can be localized - StringBuilder messageBuilder = new StringBuilder(); + List messages = new ArrayList<>(); prepare(); - validateMinAge(messageBuilder, result); - validateHistory(clearValue, messageBuilder, result); - validateStringPolicy(clearValue, messageBuilder, result); + validateMinAge(messages, result); + validateHistory(clearValue, messages, result); + validateStringPolicy(clearValue, messages, result); - String message = messageBuilder.toString(); - if (message.isEmpty()) { - result.computeStatus(); - } else { - result.computeStatus(message); + result.computeStatus(); + if (!result.isSuccess() && !messages.isEmpty()) { + result.setUserFriendlyMessage(new LocalizableMessageList(messages, LocalizableMessageList.SPACE, null, null)); } return result; } @@ -258,7 +250,7 @@ private void prepareNonce() throws SchemaException { credentialPolicy = SecurityUtil.getEffectiveNonceCredentialsPolicy(securityPolicy); } - private void validateMinAge(StringBuilder messageBuilder, OperationResult result) { + private void validateMinAge(List messages, OperationResult result) { if (oldCredentialType == null) { return; } @@ -281,25 +273,23 @@ private void validateMinAge(StringBuilder messageBuilder, OperationResult result XMLGregorianCalendar changeAllowedTimestamp = XmlTypeConverter.addDuration(lastChangeTimestamp, minAge); if (changeAllowedTimestamp.compare(now) == DatatypeConstants.GREATER) { LOGGER.trace("Password minAge violated. lastChange={}, minAge={}, now={}", lastChangeTimestamp, minAge, now); - String msg = shortDesc + " could not be changed because password minimal age was not yet reached."; - result.addSubresult(new OperationResult("Password minimal age", - OperationResultStatus.FATAL_ERROR, msg)); - messageBuilder.append(msg); - messageBuilder.append("\n"); + LocalizableMessage msg = LocalizableMessageBuilder.buildKey("ValuePolicy.minAgeNotReached"); + result.addSubresult(new OperationResult("Password minimal age", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); } } - private void validateStringPolicy(String clearValue, StringBuilder messageBuilder, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + private void validateStringPolicy(String clearValue, List messages, OperationResult result) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { if (clearValue == null) { int minOccurs = getMinOccurs(); if (minOccurs == 0) { return; } else { - String msg = shortDesc + " must have a value."; + LocalizableMessage msg = LocalizableMessageBuilder.buildKey("ValuePolicy.valueMustBePresent"); result.addSubresult(new OperationResult("minOccurs", OperationResultStatus.FATAL_ERROR, msg)); - messageBuilder.append(msg); - messageBuilder.append("\n"); + messages.add(msg); return; } } @@ -309,19 +299,19 @@ private void validateStringPolicy(String clearValue, StringBuilder messageBuilde return; } - valuePolicyProcessor.validateValue(clearValue, valuePolicy, originResolver, messageBuilder, + valuePolicyProcessor.validateValue(clearValue, valuePolicy, originResolver, messages, "user " + shortDesc + " value policy validation", task, result); } - private void validateHistory(String clearValue, StringBuilder messageBuilder, OperationResult result) throws SchemaException { + private void validateHistory(String clearValue, List messages, OperationResult result) throws SchemaException { if (!QNameUtil.match(CredentialsType.F_PASSWORD, credentialQName)) { - LOGGER.trace("Skipping validating {} history, only passowrd history is supported", shortDesc); + LOGGER.trace("Skipping validating {} history, only password history is supported", shortDesc); return; } - int historyLegth = getHistoryLength(); - if (historyLegth == 0) { + int historyLength = getHistoryLength(); + if (historyLength == 0) { LOGGER.trace("Skipping validating {} history, because history length is set to zero", shortDesc); return; } @@ -337,7 +327,7 @@ private void validateHistory(String clearValue, StringBuilder messageBuilder, Op if (passwordEquals(newPasswordPs, currentPasswordType.getValue())) { LOGGER.trace("{} matched current value", shortDesc); - appendHistoryViolationMessage(messageBuilder, result); + appendHistoryViolationMessage(messages, result); return; } @@ -345,13 +335,13 @@ private void validateHistory(String clearValue, StringBuilder messageBuilder, Op currentPasswordType.asPrismContainerValue().findContainer(PasswordType.F_HISTORY_ENTRY), false); int i = 1; for (PasswordHistoryEntryType historyEntry: sortedHistoryList) { - if (i >= historyLegth) { + if (i >= historyLength) { // success (history has more entries than needed) return; } if (passwordEquals(newPasswordPs, historyEntry.getValue())) { LOGGER.trace("Password history entry #{} matched (changed {})", i, historyEntry.getChangeTimestamp()); - appendHistoryViolationMessage(messageBuilder, result); + appendHistoryViolationMessage(messages, result); return; } i++; @@ -386,24 +376,23 @@ private List getSortedHistoryList(PrismContainer historyEntryValues = (List) historyEntries.getRealValues(); - Collections.sort(historyEntryValues, (o1, o2) -> { - XMLGregorianCalendar changeTimestampFirst = o1.getChangeTimestamp(); - XMLGregorianCalendar changeTimestampSecond = o2.getChangeTimestamp(); + historyEntryValues.sort((o1, o2) -> { + XMLGregorianCalendar changeTimestampFirst = o1.getChangeTimestamp(); + XMLGregorianCalendar changeTimestampSecond = o2.getChangeTimestamp(); - if (ascending) { - return changeTimestampFirst.compare(changeTimestampSecond); - } else { - return changeTimestampSecond.compare(changeTimestampFirst); - } - }); + if (ascending) { + return changeTimestampFirst.compare(changeTimestampSecond); + } else { + return changeTimestampSecond.compare(changeTimestampFirst); + } + }); return historyEntryValues; } - private void appendHistoryViolationMessage(StringBuilder messageBuilder, OperationResult result) { - String msg = "Password couldn't be changed because it was recently used."; + private void appendHistoryViolationMessage(List messages, OperationResult result) { + LocalizableMessage msg = LocalizableMessageBuilder.buildKey("ValuePolicy.valueRecentlyUsed"); result.addSubresult(new OperationResult("history", OperationResultStatus.FATAL_ERROR, msg)); - messageBuilder.append(msg); - messageBuilder.append("\n"); + messages.add(msg); } private String getClearValue(ProtectedStringType protectedString) { @@ -417,7 +406,7 @@ private String getClearValue(ProtectedStringType protectedString) { try { passwordStr = protector.decryptString(protectedString); } catch (EncryptionException e) { - throw new SystemException("Failed to deprypt " + shortDesc + ": " , e); + throw new SystemException("Failed to decrypt " + shortDesc + ": " + e.getMessage(), e); } } @@ -431,7 +420,7 @@ private boolean passwordEquals(ProtectedStringType newPasswordPs, ProtectedStrin try { return protector.compare(newPasswordPs, currentPassword); } catch (EncryptionException e) { - throw new SystemException("Failed to compare " + shortDesc + ": " , e); + throw new SystemException("Failed to compare " + shortDesc + ": " + e.getMessage(), e); } } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java index d6d07ae3eab..ef5fd1e6bc0 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/StringPolicyUtils.java @@ -105,11 +105,9 @@ public static String collectCharacterClass(CharacterClassType cc, QName ref) { * @return ArrayList */ public static List stringTokenizer(String in) { - List l = new ArrayList(); - for (String a: in.split("")) { - if (!a.isEmpty()) { - l.add(a); - } + List l = new ArrayList<>(); + for (int i = 0; i < in.length(); i++) { + l.add(in.substring(i, i+1)); } return l; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java index c3b4948b1c7..881da0b1ed8 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java @@ -16,20 +16,7 @@ package com.evolveum.midpoint.model.common.stringpolicy; -/** - * Processor for values that match value policies (mostly passwords). - * This class is supposed to process the parts of the value policy - * as defined in the ValuePolicyType. So it will validate the values - * and generate the values. It is NOT supposed to process - * more complex credential policies such as password lifetime - * and history. - * - * @author mamut - * @author semancik - */ - import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -39,6 +26,10 @@ import java.util.Set; import java.util.function.Consumer; +import com.evolveum.midpoint.schema.util.LocalizationUtil; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.LocalizableMessageBuilder; +import com.evolveum.midpoint.util.LocalizableMessageList; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; @@ -48,7 +39,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.crypto.EncryptionException; @@ -87,6 +77,21 @@ import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import static org.apache.commons.lang3.BooleanUtils.isTrue; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +/** + * Processor for values that match value policies (mostly passwords). + * This class is supposed to process the parts of the value policy + * as defined in the ValuePolicyType. So it will validate the values + * and generate the values. It is NOT supposed to process + * more complex credential policies such as password lifetime + * and history. + * + * @author mamut + * @author semancik + */ + @Component public class ValuePolicyProcessor { @@ -99,11 +104,17 @@ public class ValuePolicyProcessor { private static final String OPERATION_STRING_POLICY_VALIDATION = DOT_CLASS + "stringPolicyValidation"; private static final int DEFAULT_MAX_ATTEMPTS = 10; - private ItemPath path; - @Autowired private ExpressionFactory expressionFactory; @Autowired private Protector protector; + static class Context { + final ItemPath path; + + public Context(ItemPath path) { + this.path = defaultIfNull(path, SchemaConstants.PATH_PASSWORD_VALUE); + } + } + public ExpressionFactory getExpressionFactory() { return expressionFactory; } @@ -113,20 +124,9 @@ public void setExpressionFactory(ExpressionFactory expressionFactory) { this.expressionFactory = expressionFactory; } - public void setPath(ItemPath path) { - this.path = path; - } - - public ItemPath getPath() { - if (path == null) { - return SchemaConstants.PATH_PASSWORD_VALUE; - } - return path; - } - - public String generate(ItemPath path, @NotNull ValuePolicyType policy, int defaultLength, boolean generateMinimalSize, + public String generate(ItemPath path, @NotNull ValuePolicyType policy, int defaultLength, boolean generateMinimalSize, AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult parentResult) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - setPath(path); + Context ctx = new Context(path); OperationResult result = parentResult.createSubresult(OP_GENERATE); StringPolicyType stringPolicy = policy.getStringPolicy(); @@ -135,14 +135,14 @@ public String generate(ItemPath path, @NotNull ValuePoli maxAttempts = stringPolicy.getLimitations().getMaxAttempts(); } if (maxAttempts < 1) { - ExpressionEvaluationException e = new ExpressionEvaluationException("Illegal number of maximum value genaration attemps: "+maxAttempts); + ExpressionEvaluationException e = new ExpressionEvaluationException("Illegal number of maximum value generation attempts: "+maxAttempts); result.recordFatalError(e); throw e; } String generatedValue; int attempt = 1; for (;;) { - generatedValue = generateAttempt(policy, defaultLength, generateMinimalSize, result); + generatedValue = generateAttempt(policy, defaultLength, generateMinimalSize, ctx, result); if (result.isError()) { throw new ExpressionEvaluationException(result.getMessage()); } @@ -151,34 +151,35 @@ public String generate(ItemPath path, @NotNull ValuePoli } LOGGER.trace("Generator attempt {}: check failed", attempt); if (attempt == maxAttempts) { - ExpressionEvaluationException e = new ExpressionEvaluationException("Unable to genarate value, maximum number of attemps exceeded"); + ExpressionEvaluationException e = new ExpressionEvaluationException("Unable to generate value, maximum number of attempts exceeded"); result.recordFatalError(e); throw e; } attempt++; } - return generatedValue; - } public boolean validateValue(String newValue, ValuePolicyType pp, - AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return validateValue(newValue, pp, originResolver, new StringBuilder(), shortDesc, task, parentResult); + AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { + return validateValue(newValue, pp, originResolver, new ArrayList<>(), shortDesc, task, parentResult); } public boolean validateValue(String newValue, ValuePolicyType pp, - AbstractValuePolicyOriginResolver originResolver, StringBuilder message, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - + AbstractValuePolicyOriginResolver originResolver, List messages, String shortDesc, Task task, + OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { Validate.notNull(pp, "Value policy must not be null."); OperationResult result = parentResult.createSubresult(OPERATION_STRING_POLICY_VALIDATION); result.addArbitraryObjectAsParam("policyName", pp.getName()); normalize(pp); - if (newValue == null && - (pp.getMinOccurs() == null || XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()) == 0)) { - // No password is allowed + if (newValue == null && + defaultIfNull(XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()), 0) == 0) { + // No value is allowed result.recordSuccess(); return true; } @@ -189,53 +190,39 @@ public boolean validateValue(String newValue, ValuePolicy LimitationsType lims = pp.getStringPolicy().getLimitations(); - testMinimalLength(newValue, lims, result, message); - testMaximalLength(newValue, lims, result, message); + testMinimalLength(newValue, lims, result, messages); + testMaximalLength(newValue, lims, result, messages); - testMinimalUniqueCharacters(newValue, lims, result, message); - - if (lims.getLimit() == null || lims.getLimit().isEmpty()) { - if (message.toString() == null || message.toString().isEmpty()) { - result.computeStatus(); - } else { - result.computeStatus(message.toString()); + testMinimalUniqueCharacters(newValue, lims, result, messages); - } - - return result.isAcceptable(); - } + testProhibitedValues(newValue, pp.getProhibitedValues(), originResolver, shortDesc, task, result, messages); + testCheckExpression(newValue, lims, originResolver, shortDesc, task, result, messages); - // check limitation - HashSet validChars = null; - HashSet allValidChars = new HashSet<>(); - List passwd = StringPolicyUtils.stringTokenizer(newValue); - for (StringLimitType stringLimitationType : lims.getLimit()) { - OperationResult limitResult = new OperationResult( - "Tested limitation: " + stringLimitationType.getDescription()); + if (!lims.getLimit().isEmpty()) { + // check limitation + HashSet validChars; + HashSet allValidChars = new HashSet<>(); + List characters = StringPolicyUtils.stringTokenizer(newValue); + for (StringLimitType stringLimitationType : lims.getLimit()) { + OperationResult limitResult = new OperationResult("Tested limitation: " + stringLimitationType.getDescription()); - validChars = getValidCharacters(stringLimitationType.getCharacterClass(), pp); - int count = countValidCharacters(validChars, passwd); - allValidChars.addAll(validChars); - testMinimalOccurence(stringLimitationType, count, limitResult, message); - testMaximalOccurence(stringLimitationType, count, limitResult, message); - testMustBeFirst(stringLimitationType, count, limitResult, message, newValue, validChars); + validChars = getValidCharacters(stringLimitationType.getCharacterClass(), pp); + int count = countValidCharacters(validChars, characters); + allValidChars.addAll(validChars); + testMinimalOccurrence(stringLimitationType, count, limitResult, messages); + testMaximalOccurrence(stringLimitationType, count, limitResult, messages); + testMustBeFirst(stringLimitationType, limitResult, messages, newValue, validChars); - limitResult.computeStatus(); - result.addSubresult(limitResult); + limitResult.computeStatus(); + result.addSubresult(limitResult); + } + testInvalidCharacters(characters, allValidChars, result, messages); } - testInvalidCharacters(passwd, allValidChars, result, message); - - testCheckExpression(newValue, lims, originResolver, shortDesc, task, result, message); - - testProhibitedValues(newValue, pp.getProhibitedValues(), originResolver, shortDesc, task, result, message); - - if (message.toString() == null || message.toString().isEmpty()) { - result.computeStatus(); - } else { - result.computeStatus(message.toString()); + result.computeStatus(); + if (!result.isSuccess() && !messages.isEmpty()) { + result.setUserFriendlyMessage(new LocalizableMessageList(messages, LocalizableMessageList.SPACE, null, null)); } - return result.isAcceptable(); } @@ -262,70 +249,49 @@ private void normalize(ValuePolicyType pp) { lt.setMinPasswordAge(0); lt.setPasswordHistoryLength(0); } - return; } - private void testMustBeFirst(StringLimitType stringLimitationType, int count, - OperationResult limitResult, StringBuilder message, String password, Set validChars) { - // test if first character is valid - if (stringLimitationType.isMustBeFirst() == null) { - stringLimitationType.setMustBeFirst(false); - } - // we check mustBeFirst only for non-empty passwords - if (StringUtils.isNotEmpty(password) && stringLimitationType.isMustBeFirst() - && !validChars.contains(password.substring(0, 1))) { - String msg = "First character is not from allowed set. Allowed set: " + validChars.toString(); - limitResult.addSubresult( - new OperationResult("Check valid first char", OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - // else { - // limitResult.addSubresult(new OperationResult("Check valid first char - // in password OK.", - // OperationResultStatus.SUCCESS, "PASSED")); - // } - + private void testMustBeFirst(StringLimitType stringLimitation, OperationResult result, List messages, + String value, Set validFirstChars) { + if (StringUtils.isNotEmpty(value) && isTrue(stringLimitation.isMustBeFirst()) && !validFirstChars.contains(value.substring(0, 1))) { + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.firstCharacterNotAllowed") + .arg(validFirstChars.toString()) + .build(); + result.addSubresult(new OperationResult("Check valid first char", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); + } } - private void testMaximalOccurence(StringLimitType stringLimitationType, int count, - OperationResult limitResult, StringBuilder message) { - // Test maximal occurrence - if (stringLimitationType.getMaxOccurs() != null) { - - if (stringLimitationType.getMaxOccurs() < count) { - String msg = "Required maximal occurrence (" + stringLimitationType.getMaxOccurs() - + ") of characters (" + stringLimitationType.getDescription() - + ") in password was exceeded (occurrence of characters in password " + count + ")."; - limitResult.addSubresult(new OperationResult("Check maximal occurrence of characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - // else { - // limitResult.addSubresult(new OperationResult( - // "Check maximal occurrence of characters in password OK.", - // OperationResultStatus.SUCCESS, - // "PASSED")); - // } + private void testMaximalOccurrence(StringLimitType stringLimitation, int count, OperationResult result, List messages) { + if (stringLimitation.getMaxOccurs() == null) { + return; + } + if (count > stringLimitation.getMaxOccurs()) { + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.maximalOccurrenceExceeded") + .arg(stringLimitation.getMaxOccurs()) + .arg(stringLimitation.getDescription()) + .arg(count) + .build(); + result.addSubresult(new OperationResult("Check maximal occurrence of characters", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); } - } - private void testMinimalOccurence(StringLimitType stringLimitation, int count, - OperationResult result, StringBuilder message) { - // Test minimal occurrence + private void testMinimalOccurrence(StringLimitType stringLimitation, int count, OperationResult result, List messages) { if (stringLimitation.getMinOccurs() == null) { - stringLimitation.setMinOccurs(0); + return; } - if (stringLimitation.getMinOccurs() > count) { - String msg = "Required minimal occurrence (" + stringLimitation.getMinOccurs() - + ") of characters (" + stringLimitation.getDescription() - + ") in password is not met (occurrence of characters in password " + count + ")."; - result.addSubresult(new OperationResult("Check minimal occurrence of characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); + if (count < stringLimitation.getMinOccurs()) { + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.minimalOccurrenceNotMet") + .arg(stringLimitation.getMinOccurs()) + .arg(stringLimitation.getDescription()) + .arg(count) + .build(); + result.addSubresult(new OperationResult("Check minimal occurrence of characters", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); } } @@ -342,130 +308,117 @@ private int countValidCharacters(Set validChars, List password) private HashSet getValidCharacters(CharacterClassType characterClassType, ValuePolicyType passwordPolicy) { if (null != characterClassType.getValue()) { - return new HashSet(StringPolicyUtils.stringTokenizer(characterClassType.getValue())); + return new HashSet<>(StringPolicyUtils.stringTokenizer(characterClassType.getValue())); } else { - return new HashSet(StringPolicyUtils.stringTokenizer(StringPolicyUtils + return new HashSet<>(StringPolicyUtils.stringTokenizer(StringPolicyUtils .collectCharacterClass(passwordPolicy.getStringPolicy().getCharacterClass(), characterClassType.getRef()))); } } private void testMinimalUniqueCharacters(String password, LimitationsType limitations, - OperationResult result, StringBuilder message) { - // Test uniqueness criteria - HashSet tmp = new HashSet(StringPolicyUtils.stringTokenizer(password)); - if (limitations.getMinUniqueChars() != null) { - if (limitations.getMinUniqueChars() > tmp.size()) { - String msg = "Required minimal count of unique characters (" + limitations.getMinUniqueChars() - + ") in password are not met (unique characters in password " + tmp.size() + ")"; - result.addSubresult(new OperationResult("Check minimal count of unique chars", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - + OperationResult result, List message) { + if (limitations.getMinUniqueChars() == null) { + return; + } + HashSet distinctCharacters = new HashSet<>(StringPolicyUtils.stringTokenizer(password)); + if (limitations.getMinUniqueChars() > distinctCharacters.size()) { + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.minimalUniqueCharactersNotMet") + .arg(limitations.getMinUniqueChars()) + .arg(distinctCharacters.size()) + .build(); + result.addSubresult(new OperationResult("Check minimal count of unique chars", OperationResultStatus.FATAL_ERROR, msg)); + message.add(msg); } } - private void testMinimalLength(String password, LimitationsType limitations, - OperationResult result, StringBuilder message) { - // Test minimal length + private void testMinimalLength(String value, LimitationsType limitations, OperationResult result, List messages) { if (limitations.getMinLength() == null) { - limitations.setMinLength(0); + return; } - if (limitations.getMinLength() > password.length()) { - String msg = "Required minimal size (" + limitations.getMinLength() - + ") is not met (actual length: " + password.length() + ")"; - result.addSubresult(new OperationResult("Check global minimal length", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); + if (value.length() < limitations.getMinLength()) { + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.minimalSizeNotMet") + .arg(limitations.getMinLength()) + .arg(value.length()) + .build(); + result.addSubresult(new OperationResult("Check global minimal length", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); } } - private void testMaximalLength(String password, LimitationsType limitations, - OperationResult result, StringBuilder message) { - // Test maximal length - if (limitations.getMaxLength() != null) { - if (limitations.getMaxLength() < password.length()) { - String msg = "Required maximal size (" + limitations.getMaxLength() - + ") was exceeded (actual length: " + password.length() + ")."; - result.addSubresult(new OperationResult("Check global maximal length", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } + private void testMaximalLength(String value, LimitationsType limitations, OperationResult result, List messages) { + if (limitations.getMaxLength() == null) { + return; + } + if (value.length() > limitations.getMaxLength()) { + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.maximalSizeExceeded") + .arg(limitations.getMaxLength()) + .arg(value.length()) + .build(); + result.addSubresult(new OperationResult("Check global maximal length", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); } } - private void testInvalidCharacters(List password, HashSet validChars, - OperationResult result, StringBuilder message) { - - // Check if there is no invalid character + private void testInvalidCharacters(List valueCharacters, HashSet validChars, OperationResult result, List message) { StringBuilder invalidCharacters = new StringBuilder(); - for (String s : password) { - if (!validChars.contains(s)) { - // memorize all invalid characters - invalidCharacters.append(s); + for (String character : valueCharacters) { + if (!validChars.contains(character)) { + invalidCharacters.append(character); } } if (invalidCharacters.length() > 0) { - String msg = "Characters [ " + invalidCharacters + " ] are not allowed in value"; - result.addSubresult(new OperationResult("Check if value does not contain invalid characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } - // else { - // ret.addSubresult(new OperationResult("Check if password does not - // contain invalid characters OK.", - // OperationResultStatus.SUCCESS, "PASSED")); - // } - + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.charactersNotAllowed") + .arg(invalidCharacters) + .build(); + result.addSubresult(new OperationResult("Check if value does not contain invalid characters", OperationResultStatus.FATAL_ERROR, msg)); + message.add(msg); + } } - private void testCheckExpression(String newPassword, LimitationsType lims, AbstractValuePolicyOriginResolver originResolver, - String shortDesc, Task task, OperationResult result, StringBuilder message) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - List checkExpressions = lims.getCheckExpression(); - if (checkExpressions.isEmpty()) { - return; - } - for (CheckExpressionType checkExpression: checkExpressions) { + private void testCheckExpression(String newPassword, LimitationsType lims, + AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult result, + List messages) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { + for (CheckExpressionType checkExpression: lims.getCheckExpression()) { ExpressionType expressionType = checkExpression.getExpression(); if (expressionType == null) { return; } if (!checkExpression(newPassword, expressionType, originResolver, shortDesc, task, result)) { - String msg = checkExpression.getFailureMessage(); - if (msg == null) { - msg = "Check expression failed"; + LocalizableMessage msg; + if (checkExpression.getLocalizableFailureMessage() != null) { + msg = LocalizationUtil.toLocalizableMessage(checkExpression.getLocalizableFailureMessage()); + } else if (checkExpression.getFailureMessage() != null) { + msg = LocalizableMessageBuilder.buildFallbackMessage(checkExpression.getFailureMessage()); + } else { + msg = LocalizableMessageBuilder.buildKey("ValuePolicy.checkExpressionFailed"); } - result.addSubresult(new OperationResult("Check expression", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); + result.addSubresult(new OperationResult("Check expression", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); } } - } - private void testProhibitedValues(String newPassword, ProhibitedValuesType prohibitedValuesType, AbstractValuePolicyOriginResolver originResolver, - String shortDesc, Task task, OperationResult result, StringBuilder message) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - + private void testProhibitedValues(String newPassword, ProhibitedValuesType prohibitedValuesType, + AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult result, + List messages) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { if (prohibitedValuesType == null || originResolver == null) { return; } - Consumer failAction = (prohibitedItemType) -> { - String msg = "The value is prohibited. Choose a different value."; - result.addSubresult(new OperationResult("Prohibited value", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); + LocalizableMessage msg = new LocalizableMessageBuilder() + .key("ValuePolicy.prohibitedValue") + .build(); + result.addSubresult(new OperationResult("Prohibited value", OperationResultStatus.FATAL_ERROR, msg)); + messages.add(msg); }; checkProhibitedValues(newPassword, prohibitedValuesType, originResolver, failAction, shortDesc, task, result); - } private boolean checkProhibitedValues(String newPassword, ProhibitedValuesType prohibitedValuesType, AbstractValuePolicyOriginResolver originResolver, @@ -532,8 +485,7 @@ private boolean isMatching(String newPassword, PrismProperty objectPrope return false; } - private String generateAttempt(ValuePolicyType policy, int defaultLength, boolean generateMinimalSize, - OperationResult result) { + private String generateAttempt(ValuePolicyType policy, int defaultLength, boolean generateMinimalSize, Context ctx, OperationResult result) { StringPolicyType stringPolicy = policy.getStringPolicy(); // if (policy.getLimitations() != null && @@ -545,7 +497,7 @@ private String generateAttempt(ValuePolicyType policy, int defaultLength, boolea // Optimize usage of limits ass hashmap of limitas and key is set of // valid chars for each limitation - Map> lims = new HashMap>(); + Map> lims = new HashMap<>(); int minLen = defaultLength; int maxLen = defaultLength; int unique = defaultLength / 2; @@ -560,23 +512,18 @@ private String generateAttempt(ValuePolicyType policy, int defaultLength, boolea } // Get global limitations - minLen = stringPolicy.getLimitations().getMinLength() == null ? 0 - : stringPolicy.getLimitations().getMinLength().intValue(); + minLen = defaultIfNull(stringPolicy.getLimitations().getMinLength(), 0); if (minLen != 0 && minLen > defaultLength) { defaultLength = minLen; } - maxLen = (stringPolicy.getLimitations().getMaxLength() == null ? 0 - : stringPolicy.getLimitations().getMaxLength().intValue()); - unique = stringPolicy.getLimitations().getMinUniqueChars() == null ? minLen - : stringPolicy.getLimitations().getMinUniqueChars().intValue(); - - } + maxLen = defaultIfNull(stringPolicy.getLimitations().getMaxLength(), 0); + unique = defaultIfNull(stringPolicy.getLimitations().getMinUniqueChars(), minLen); + } // test correctness of definition if (unique > minLen) { minLen = unique; OperationResult reportBug = new OperationResult("Global limitation check"); - reportBug.recordWarning( - "There is more required uniq characters then definied minimum. Raise minimum to number of required uniq chars."); + reportBug.recordWarning("There is more required unique characters then defined minimum. Raise minimum to number of required unique chars."); } if (minLen == 0 && maxLen == 0) { @@ -621,7 +568,7 @@ private String generateAttempt(ValuePolicyType policy, int defaultLength, boolea // Log error if (LOGGER.isErrorEnabled()) { LOGGER.error( - "Unable to generate value for " + getPath() + ": No intersection for required first character sets in value policy: [" + "Unable to generate value for " + ctx.path + ": No intersection for required first character sets in value policy: [" + stringPolicy.getDescription() + "] following character limitation and sets are used:"); for (StringLimitType l : mustBeFirst.keySet()) { @@ -638,7 +585,7 @@ private String generateAttempt(ValuePolicyType policy, int defaultLength, boolea StrBuilder tmp = new StrBuilder(); tmp.appendSeparator(", "); tmp.appendAll(intersectionCharacters); - LOGGER.trace("Generate first character intersection items [" + tmp + "] into " + getPath() + "."); + LOGGER.trace("Generate first character intersection items [" + tmp + "] into " + ctx.path + "."); } // Generate random char into password from intersection password.append(intersectionCharacters.get(RAND.nextInt(intersectionCharacters.size()))); @@ -685,7 +632,7 @@ private String generateAttempt(ValuePolicyType policy, int defaultLength, boolea // test if maximum is not exceeded if (password.length() > maxLen) { result.recordFatalError( - "Unable to meet minimal criteria and not exceed maximxal size of " + getPath() + "."); + "Unable to meet minimal criteria and not exceed maximal size of " + ctx.path + "."); return null; } @@ -751,10 +698,10 @@ private String generateAttempt(ValuePolicyType policy, int defaultLength, boolea if (password.length() < minLen) { result.recordFatalError( - "Unable to generate value for " + getPath() + " and meet minimal size of " + getPath() + ". Actual lenght: " + "Unable to generate value for " + ctx.path + " and meet minimal size of " + ctx.path + ". Actual length: " + password.length() + ", required: " + minLen); LOGGER.trace( - "Unable to generate value for " + getPath() + " and meet minimal size of " + getPath() + ". Actual lenght: {}, required: {}", + "Unable to generate value for " + ctx.path + " and meet minimal size of " + ctx.path + ". Actual length: {}, required: {}", password.length(), minLen); return null; } @@ -800,7 +747,10 @@ private boolean checkExpressions(String generatedValue, L return true; } - public boolean checkExpression(String generatedValue, ExpressionType checkExpression, AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + private boolean checkExpression(String generatedValue, ExpressionType checkExpression, + AbstractValuePolicyOriginResolver originResolver, String shortDesc, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { ExpressionVariables variables = new ExpressionVariables(); variables.addVariableDefinition(ExpressionConstants.VAR_INPUT, generatedValue); variables.addVariableDefinition(ExpressionConstants.VAR_OBJECT, originResolver == null ? null : originResolver.getObject()); @@ -813,7 +763,7 @@ public boolean checkExpression(String generatedValue, Exp */ private Map> cardinalityCounter(Map> lims, List password, Boolean skipMatchedLims, boolean uniquenessReached, OperationResult op) { - HashMap counter = new HashMap(); + HashMap counter = new HashMap<>(); for (StringLimitType l : lims.keySet()) { int counterKey = 1; @@ -871,12 +821,10 @@ private Map> cardinalityCounter(Map> ret = new HashMap>(); + Map> ret = new HashMap<>(); for (String s : counter.keySet()) { // if not there initialize - if (null == ret.get(counter.get(s))) { - ret.put(counter.get(s), new ArrayList()); - } + ret.computeIfAbsent(counter.get(s), k -> new ArrayList<>()); ret.get(counter.get(s)).add(s); } return ret; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index 8a3d07cf673..74901c72582 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -1416,6 +1416,6 @@ public LocalizableMessageType createLocalizableMessageType(LocalizableMessageTem ConfigurationException, SecurityViolationException { ExpressionVariables vars = new ExpressionVariables(); vars.addVariableDefinitions(variables); - return LensUtil.createLocalizableMessageType(template, vars, expressionFactory, prismContext, task, result); + return LensUtil.interpretLocalizableMessageTemplate(template, vars, expressionFactory, prismContext, task, result); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java index fca8a18dad0..725199e4298 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java @@ -94,7 +94,7 @@ import java.util.*; import java.util.stream.Collectors; -import static com.evolveum.midpoint.schema.util.LocalizationUtil.parseLocalizableMessageType; +import static com.evolveum.midpoint.schema.util.LocalizationUtil.toLocalizableMessage; import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.RUNNABLE; import static java.util.Collections.singleton; @@ -1584,6 +1584,6 @@ public String translate(LocalizableMessage message) { @Override public String translate(LocalizableMessageType message) { - return localizationService.translate(parseLocalizableMessageType(message), Locale.getDefault()); + return localizationService.translate(LocalizationUtil.toLocalizableMessage(message), Locale.getDefault()); } } \ No newline at end of file diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 3e64cbaebb9..371c1ae9c6f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -1029,7 +1029,7 @@ public static LocalizableMessageType evaluateLocalizableMessageType(ExpressionTy } else if (o instanceof LocalizableMessage) { return LocalizationUtil.createLocalizableMessageType((LocalizableMessage) o); } else { - return new LocalizableMessageType().fallbackMessage(String.valueOf(o)); + return new SingleLocalizableMessageType().fallbackMessage(String.valueOf(o)); } }; return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, @@ -1056,12 +1056,12 @@ public static T evaluateExpressionSingle(ExpressionType expressionBean, Expr } @NotNull - public static LocalizableMessageType createLocalizableMessageType(LocalizableMessageTemplateType template, + public static SingleLocalizableMessageType interpretLocalizableMessageTemplate(LocalizableMessageTemplateType template, ExpressionVariables var, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - LocalizableMessageType rv = new LocalizableMessageType(); + SingleLocalizableMessageType rv = new SingleLocalizableMessageType(); if (template.getKey() != null) { rv.setKey(template.getKey()); } else if (template.getKeyExpression() != null) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialPolicyEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialPolicyEvaluator.java index 79a778467e2..26bbb4714aa 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialPolicyEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialPolicyEvaluator.java @@ -49,11 +49,11 @@ import com.evolveum.midpoint.prism.delta.PartiallyResolvedDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.LocalizableMessageBuilder; +import com.evolveum.midpoint.util.SingleLocalizableMessage; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -70,7 +70,6 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordHistoryEntryType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityPolicyType; @@ -96,7 +95,7 @@ public abstract class CredentialPolicyEvaluator cVal) th } } - protected void validateProtectedStringValue(ProtectedStringType value) throws PolicyViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - + protected void validateProtectedStringValue(ProtectedStringType value) throws PolicyViolationException, SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, + SecurityViolationException { + OperationResult validationResult = getObjectValuePolicyEvaluator().validateProtectedStringValue(value); result.addSubresult(validationResult); if (!validationResult.isAcceptable()) { - throw new PolicyViolationException("Provided "+getCredentialHumanReadableName()+" does not satisfy the policies: " + validationResult.getMessage()); + SingleLocalizableMessage message = new LocalizableMessageBuilder() + .key("PolicyViolationException.message.credentials." + getCredentialHumanReadableKey()) + .arg(validationResult.getUserFriendlyMessage()) + .build(); + throw new PolicyViolationException(message); } } @@ -378,9 +384,7 @@ context, getCredentialsContainerPath().subPath(AbstractCredentialType.F_METADATA } } - private void addMissingMetadata(PrismContainerValue cVal) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - + private void addMissingMetadata(PrismContainerValue cVal) throws SchemaException { if (hasValueChange(cVal)) { if (!hasMetadata(cVal)) { MetadataType metadataType = metadataManager.createCreateMetadata(context, now, task); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/NoncePolicyEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/NoncePolicyEvaluator.java index 9c231b1b4be..7a44728095f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/NoncePolicyEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/NoncePolicyEvaluator.java @@ -45,6 +45,11 @@ protected String getCredentialHumanReadableName() { return "nonce"; } + @Override + protected String getCredentialHumanReadableKey() { + return "nonce"; + } + @Override protected boolean supportsHistory() { return false; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/PasswordPolicyEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/PasswordPolicyEvaluator.java index 70fb33d87e8..05ab94824a5 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/PasswordPolicyEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/PasswordPolicyEvaluator.java @@ -40,6 +40,11 @@ protected String getCredentialHumanReadableName() { return "password"; } + @Override + protected String getCredentialHumanReadableKey() { + return "password"; + } + @Override protected boolean supportsHistory() { return true; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java index 0ac016904dc..a0b0768a3ba 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java @@ -22,13 +22,13 @@ import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.util.LocalizableMessageBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.common.stringpolicy.AbstractValuePolicyOriginResolver; import com.evolveum.midpoint.model.common.stringpolicy.ShadowValuePolicyOriginResolver; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.ModelObjectResolver; @@ -58,8 +58,6 @@ import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.repo.common.expression.ItemDeltaItem; import com.evolveum.midpoint.repo.common.expression.Source; import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; @@ -86,8 +84,6 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; @@ -372,7 +368,12 @@ private void validateProjectionPassword(LensContext con if (!isValid) { result.computeStatus(); - throw new PolicyViolationException("Provided password does not satisfy password policies in " + projectionContext.getHumanReadableName() + ": " + result.getMessage()); + throw new PolicyViolationException( + new LocalizableMessageBuilder() + .key("PolicyViolationException.message.projectionPassword") + .arg(projectionContext.getHumanReadableName()) + .arg(result.getUserFriendlyMessage()) + .build()); } } @@ -382,7 +383,7 @@ private ShadowValuePolicyOriginResolver getOriginResolver(PrismObject void applyMetadata(LensContext context, final LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + throws SchemaException { ObjectDelta accountDelta = projectionContext.getDelta(); @@ -390,7 +391,7 @@ private void applyMetadata(LensContext context, return; } - if (accountDelta == null){ + if (accountDelta == null) { LOGGER.trace("Skipping application of password metadata. Shadow delta not specified."); return; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/SecurityQuestionsPolicyEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/SecurityQuestionsPolicyEvaluator.java index 8341ffcc880..83d2ac5021e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/SecurityQuestionsPolicyEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/SecurityQuestionsPolicyEvaluator.java @@ -46,8 +46,12 @@ protected String getCredentialHumanReadableName() { } @Override - protected SecurityQuestionsCredentialsPolicyType determineEffectiveCredentialPolicy() - throws SchemaException { + protected String getCredentialHumanReadableKey() { + return "securityQuestions"; + } + + @Override + protected SecurityQuestionsCredentialsPolicyType determineEffectiveCredentialPolicy() { return SecurityUtil.getEffectiveSecurityQuestionsCredentialsPolicy(getSecurityPolicy()); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ConstraintEvaluatorHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ConstraintEvaluatorHelper.java index d23f575d7bb..35a2d149c76 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ConstraintEvaluatorHelper.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ConstraintEvaluatorHelper.java @@ -91,10 +91,10 @@ public String evaluateString(ExpressionType expressionBean, ExpressionVariables task, result); } - public LocalizableMessageType createLocalizableMessageType(LocalizableMessageTemplateType template, + public SingleLocalizableMessageType interpretLocalizableMessageTemplate(LocalizableMessageTemplateType template, PolicyRuleEvaluationContext rctx, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - return LensUtil.createLocalizableMessageType(template, createExpressionVariables(rctx), expressionFactory, prismContext, rctx.task, result); + return LensUtil.interpretLocalizableMessageTemplate(template, createExpressionVariables(rctx), expressionFactory, prismContext, rctx.task, result); } public LocalizableMessage createLocalizableMessage( @@ -102,12 +102,9 @@ public LocalizableMessage createLocalizableMessage( LocalizableMessage builtInMessage, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { if (constraint.getPresentation() != null && constraint.getPresentation().getMessage() != null) { - LocalizableMessageType messageType = - createLocalizableMessageType(constraint.getPresentation().getMessage(), rctx, result); - return LocalizationUtil.parseLocalizableMessageType(messageType, - // if user-configured fallback message is present; we ignore the built-in constraint message - // TODO consider ignoring it always if custom presentation/message is provided - messageType.getFallbackMessage() != null ? null : builtInMessage); + SingleLocalizableMessageType messageType = + interpretLocalizableMessageTemplate(constraint.getPresentation().getMessage(), rctx, result); + return LocalizationUtil.toLocalizableMessage(messageType); } else if (constraint.getName() != null) { return new LocalizableMessageBuilder() .key(SchemaConstants.POLICY_CONSTRAINT_KEY_PREFIX + constraint.getName()) @@ -123,12 +120,9 @@ public LocalizableMessage createLocalizableShortMessage( LocalizableMessage builtInMessage, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { if (constraint.getPresentation() != null && constraint.getPresentation().getShortMessage() != null) { - LocalizableMessageType messageType = - createLocalizableMessageType(constraint.getPresentation().getShortMessage(), rctx, result); - return LocalizationUtil.parseLocalizableMessageType(messageType, - // if user-configured fallback message is present; we ignore the built-in constraint message - // TODO consider ignoring it always if custom presentation/message is provided - messageType.getFallbackMessage() != null ? null : builtInMessage); + SingleLocalizableMessageType messageType = + interpretLocalizableMessageTemplate(constraint.getPresentation().getShortMessage(), rctx, result); + return LocalizationUtil.toLocalizableMessage(messageType); } else if (constraint.getName() != null) { return new LocalizableMessageBuilder() .key(SchemaConstants.POLICY_CONSTRAINT_SHORT_MESSAGE_KEY_PREFIX + constraint.getName()) diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index 16e0944b1c9..8a68b968149 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -46,6 +46,7 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.util.*; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.mutable.MutableInt; import org.jetbrains.annotations.NotNull; @@ -157,13 +158,6 @@ import com.evolveum.midpoint.test.IntegrationTestTools; import com.evolveum.midpoint.test.util.MidPointAsserts; import com.evolveum.midpoint.test.util.TestUtil; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.DisplayableValue; -import com.evolveum.midpoint.util.FailableProcessor; -import com.evolveum.midpoint.util.Holder; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.Producer; -import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.CommonException; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; @@ -4683,10 +4677,10 @@ protected void assertPasswordCompliesWithPolicy(PrismObject user, Stri String password = getPassword(user); display("Password of "+user, password); PrismObject passwordPolicy = repositoryService.getObject(ValuePolicyType.class, passwordPolicyOid, null, result); - StringBuilder messageBuilder = new StringBuilder(); - boolean valid = valuePolicyProcessor.validateValue(password, passwordPolicy.asObjectable(), createUserOriginResolver(user), messageBuilder, "validating password of "+user, task, result); + List messages = new ArrayList<>(); + boolean valid = valuePolicyProcessor.validateValue(password, passwordPolicy.asObjectable(), createUserOriginResolver(user), messages, "validating password of "+user, task, result); if (!valid) { - fail("Password for "+user+" does not comply with password policy: "+messageBuilder.toString()); + fail("Password for "+user+" does not comply with password policy: "+messages); } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java index 85d647dd95b..6840e155f39 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java @@ -37,7 +37,6 @@ import com.evolveum.midpoint.schema.ObjectTreeDeltas; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.LocalizationUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.LocalizableMessage; @@ -277,7 +276,7 @@ private PcpChildWfTaskCreationInstruction prepareAs Validate.notNull(target, "assignment target is null"); LocalizableMessage processName = main.createProcessName(builderResult, evaluatedAssignment, ctx, result); - if (LocalizationUtil.isEmpty(processName) || PolicyRuleBasedAspect.USE_DEFAULT_NAME_MARKER.equals(processName.getKey())) { + if (main.useDefaultProcessName(processName)) { processName = createDefaultProcessName(modelContext, assignmentMode, target); } String processNameInDefaultLocale = localizationService.translate(processName, Locale.getDefault()); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java index 24cb0d03722..980690dfd0b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java @@ -183,7 +183,7 @@ private void prepareObjectRelatedTaskInstructions( for (ObjectDelta deltaToApprove : deltasToApprove) { LocalizableMessage processName = main.createProcessName(builderResult, null, ctx, result); - if (LocalizationUtil.isEmpty(processName) || PolicyRuleBasedAspect.USE_DEFAULT_NAME_MARKER.equals(processName.getKey())) { + if (main.useDefaultProcessName(processName)) { processName = createDefaultProcessName(modelContext, deltaToApprove); } String processNameInDefaultLocale = localizationService.translate(processName, Locale.getDefault()); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java index 6b3303e80b3..2cf684ddb9b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/PolicyRuleBasedAspect.java @@ -30,6 +30,7 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.LocalizationUtil; import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.SingleLocalizableMessage; import com.evolveum.midpoint.util.TreeNode; import com.evolveum.midpoint.util.exception.CommonException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; @@ -73,7 +74,7 @@ public class PolicyRuleBasedAspect extends BasePrimaryChangeAspect { @Autowired private ObjectPolicyAspectPart objectPolicyAspectPart; @Autowired private ModelInteractionService modelInteractionService; - //region ------------------------------------------------------------ Things that execute on request arrival + //region ------------------------------------------------------------ Things that execute on request arrival @Override public boolean isEnabledByDefault() { @@ -162,7 +163,7 @@ private LocalizableMessage processNameFromApprovalActions(ApprovalSchemaBuilder. } catch (CommonException|RuntimeException e) { throw new SystemException("Couldn't create localizable message for approval display name: " + e.getMessage(), e); } - return LocalizationUtil.parseLocalizableMessageType(localizableMessageType); + return LocalizationUtil.toLocalizableMessage(localizableMessageType); } @Nullable @@ -194,10 +195,16 @@ private LocalizableMessage processNameFromTriggers(ApprovalSchemaBuilder.Result // now get the first message List> trees = EvaluatedPolicyRuleUtil.arrangeForPresentationExt(triggers); if (!trees.isEmpty() && trees.get(0).getUserObject().getShortMessage() != null) { - return LocalizationUtil.parseLocalizableMessageType(trees.get(0).getUserObject().getShortMessage()); + return LocalizationUtil.toLocalizableMessage(trees.get(0).getUserObject().getShortMessage()); } else { return null; } } + + protected boolean useDefaultProcessName(LocalizableMessage processName) { + return LocalizableMessage.isEmpty(processName) || + processName instanceof SingleLocalizableMessage && + USE_DEFAULT_NAME_MARKER.equals(((SingleLocalizableMessage) processName).getKey()); + } //endregion } \ No newline at end of file From 92c415a2cabde780344d7d33718b7088bb43dfce Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 5 Dec 2017 12:28:01 +0100 Subject: [PATCH 2/4] Parsing abstractly typed fields (like LocalizableMessageType) without the need for xsi:type --- ...ositeRefinedObjectClassDefinitionImpl.java | 12 ++ ...LayerRefinedObjectClassDefinitionImpl.java | 12 ++ .../RefinedObjectClassDefinitionImpl.java | 15 +- .../midpoint/prism/ComplexTypeDefinition.java | 4 + .../prism/ComplexTypeDefinitionImpl.java | 7 + .../evolveum/midpoint/prism/Definition.java | 1 + .../midpoint/prism/PrismConstants.java | 1 + .../midpoint/prism/TypeDefinition.java | 10 + .../midpoint/prism/TypeDefinitionImpl.java | 26 +++ .../prism/marshaller/BeanMarshaller.java | 10 +- .../prism/marshaller/BeanUnmarshaller.java | 21 +- .../prism/marshaller/PrismBeanInspector.java | 194 +++++++++--------- .../schema/DomToSchemaPostProcessor.java | 15 +- .../prism/schema/SchemaProcessorUtil.java | 12 +- .../prism/schema/SchemaRegistryImpl.java | 16 +- .../resources/xml/ns/public/annotation-3.xsd | 13 ++ .../xml/ns/public/common/common-core-3.xsd | 3 + .../resources/common/xml/ns/wf-context.xml | 21 +- .../model/api/expr/MidpointFunctions.java | 55 ++--- 19 files changed, 309 insertions(+), 139 deletions(-) diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/CompositeRefinedObjectClassDefinitionImpl.java b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/CompositeRefinedObjectClassDefinitionImpl.java index 9406ad9d3de..e7be0b57a43 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/CompositeRefinedObjectClassDefinitionImpl.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/CompositeRefinedObjectClassDefinitionImpl.java @@ -40,6 +40,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Collections.emptySet; + /** * Used to represent combined definition of structural and auxiliary object classes. * @@ -709,4 +711,14 @@ public boolean isReferenceMarker() { return structuralObjectClassDefinition.isReferenceMarker(); } + @NotNull + @Override + public Collection getStaticSubTypes() { + return emptySet(); + } + + @Override + public Integer getInstantiationOrder() { + return null; + } } diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedObjectClassDefinitionImpl.java b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedObjectClassDefinitionImpl.java index 82c0d972209..44f27d3ef1d 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedObjectClassDefinitionImpl.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedObjectClassDefinitionImpl.java @@ -37,6 +37,8 @@ import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import org.jetbrains.annotations.NotNull; +import static java.util.Collections.emptySet; + /** * @author semancik * @author mederly @@ -643,4 +645,14 @@ public boolean isShared() { } } + @NotNull + @Override + public Collection getStaticSubTypes() { + return emptySet(); // not supported for now (this type itself is not statically defined) + } + + @Override + public Integer getInstantiationOrder() { + return null; + } } diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedObjectClassDefinitionImpl.java b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedObjectClassDefinitionImpl.java index b12e1d3a201..75c1621456a 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedObjectClassDefinitionImpl.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedObjectClassDefinitionImpl.java @@ -52,6 +52,8 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import static java.util.Collections.emptySet; + /** * @author semancik */ @@ -634,6 +636,12 @@ public PrismContext getPrismContext() { return originalObjectClassDefinition.getPrismContext(); } + @NotNull + @Override + public Collection getStaticSubTypes() { + return emptySet(); // not supported for now (this type itself is not statically defined) + } + @Nullable @Override public Class getCompileTimeClass() { @@ -1305,5 +1313,10 @@ public void trimTo(@NotNull Collection paths) { public boolean isShared() { return shared; } - + + @Override + public Integer getInstantiationOrder() { + return null; + } + } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinition.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinition.java index 63771af7cd6..e5c94af1dc9 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinition.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinition.java @@ -147,4 +147,8 @@ public interface ComplexTypeDefinition extends TypeDefinition, LocalDefinitionSt * USE WITH CARE. Be sure no shared definitions would be affected by this operation! */ void trimTo(@NotNull Collection paths); + + default boolean containsItemDefinition(QName itemName) { + return findItemDefinition(itemName) != null; + } } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinitionImpl.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinitionImpl.java index 36a7616e0ff..07de107abb2 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinitionImpl.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/ComplexTypeDefinitionImpl.java @@ -197,6 +197,7 @@ public PrismPropertyDefinition createPropertyDefinition(String localName, String //region Finding definitions // TODO deduplicate w.r.t. findNamedItemDefinition + // but beware, consider only local definitions! @Override public T findItemDefinition(@NotNull QName name, @NotNull Class clazz, boolean caseInsensitive) { for (ItemDefinition def : getDefinitions()) { @@ -442,6 +443,12 @@ public String debugDump(int indent, IdentityHashMap seen) { if (xsdAnyMarker) { sb.append(",Ma"); } + if (instantiationOrder != null) { + sb.append(",o:").append(instantiationOrder); + } + if (!staticSubTypes.isEmpty()) { + sb.append(",st:").append(staticSubTypes.size()); + } extendDumpHeader(sb); if (seen.containsKey(this)) { sb.append(" (already shown)"); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/Definition.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/Definition.java index b359237aaf3..6b7c2229e91 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/Definition.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/Definition.java @@ -163,6 +163,7 @@ default SchemaRegistry getSchemaRegistry() { // TODO fix this! Class getTypeClassIfKnown(); + // todo suspicious, please investigate and document Class getTypeClass(); @NotNull diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismConstants.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismConstants.java index 02842aa2bca..09c3a5b6713 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismConstants.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismConstants.java @@ -57,6 +57,7 @@ public class PrismConstants { public static final QName A_PROPERTY_CONTAINER = new QName(NS_ANNOTATION, "container"); public static final QName A_OBJECT = new QName(NS_ANNOTATION, "object"); + public static final QName A_INSTANTIATION_ORDER = new QName(NS_ANNOTATION, "instantiationOrder"); public static final QName A_DEFAULT_NAMESPACE = new QName(NS_ANNOTATION, "defaultNamespace"); public static final QName A_IGNORED_NAMESPACE = new QName(NS_ANNOTATION, "ignoredNamespace"); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinition.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinition.java index deae58dd1e4..a3803a3d0e7 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinition.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinition.java @@ -16,9 +16,11 @@ package com.evolveum.midpoint.prism; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.xml.namespace.QName; +import java.util.Collection; /** * @author mederly @@ -38,4 +40,12 @@ public interface TypeDefinition extends Definition { */ @Nullable QName getSuperType(); + + /** + * Subtypes - but only these that are a part of the static schema. A little bit experimental. :) + */ + @NotNull + Collection getStaticSubTypes(); + + Integer getInstantiationOrder(); } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinitionImpl.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinitionImpl.java index e07b11557c8..25df5290943 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinitionImpl.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/TypeDefinitionImpl.java @@ -16,8 +16,13 @@ package com.evolveum.midpoint.prism; +import org.jetbrains.annotations.NotNull; + import javax.xml.namespace.QName; +import java.util.Collection; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; /** * @author mederly @@ -26,6 +31,8 @@ public abstract class TypeDefinitionImpl extends DefinitionImpl implements TypeD protected QName superType; protected Class compileTimeClass; + @NotNull protected final Set staticSubTypes = new HashSet<>(); + protected Integer instantiationOrder; public TypeDefinitionImpl(QName typeName, PrismContext prismContext) { super(typeName, prismContext); @@ -40,6 +47,25 @@ public void setSuperType(QName superType) { this.superType = superType; } + @Override + @NotNull + public Collection getStaticSubTypes() { + return staticSubTypes; + } + + public void addStaticSubType(TypeDefinition subtype) { + staticSubTypes.add(subtype); + } + + @Override + public Integer getInstantiationOrder() { + return instantiationOrder; + } + + public void setInstantiationOrder(Integer instantiationOrder) { + this.instantiationOrder = instantiationOrder; + } + @Override public Class getCompileTimeClass() { return compileTimeClass; diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java index e6834f6d7d7..b39b17f5ba1 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanMarshaller.java @@ -249,17 +249,17 @@ private XNode marshalXmlTypeToMap(Object bean, SerializationContext ctx) throws } else { QName fieldTypeName = inspector.findTypeName(field, getterResult.getClass(), namespace); Object valueToMarshall; - if (getterResult instanceof JAXBElement){ + if (getterResult instanceof JAXBElement) { valueToMarshall = ((JAXBElement) getterResult).getValue(); elementName = ((JAXBElement) getterResult).getName(); - } else{ + } else { valueToMarshall = getterResult; } XNode marshaled = marshallValue(valueToMarshall, fieldTypeName, isAttribute, ctx); - // TODO reconcile with setExplioitTypeDeclarationIfNeeded + // TODO reconcile with setExplicitTypeDeclarationIfNeeded if (!getter.getReturnType().equals(valueToMarshall.getClass()) && getter.getReturnType().isAssignableFrom(valueToMarshall.getClass()) && !(valueToMarshall instanceof Enum)) { - PrismObjectDefinition def = prismContext.getSchemaRegistry().determineDefinitionFromClass(valueToMarshall.getClass()); - if (def != null){ + TypeDefinition def = prismContext.getSchemaRegistry().findTypeDefinitionByCompileTimeClass(valueToMarshall.getClass(), TypeDefinition.class); + if (def != null) { QName type = def.getTypeName(); marshaled.setTypeQName(type); marshaled.setExplicitTypeDeclaration(true); diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java index 1d2f71a3df3..45bcd5d62a9 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/BeanUnmarshaller.java @@ -47,6 +47,8 @@ import java.util.Map.Entry; import java.util.stream.Collectors; +import static java.util.Collections.emptySet; + /** * Analogous to PrismUnmarshaller, this class unmarshals atomic values from XNode tree structures. * Atomic values are values that can be used as property values (i.e. either simple types, or @@ -191,7 +193,7 @@ private T unmarshalInternal(@NotNull XNode xnode, @NotNull Class beanClas */ private T unmarshallPrimitive(PrimitiveXNode prim, Class beanClass, ParsingContext pc) throws SchemaException { if (prim.isEmpty()) { - return instantiate(beanClass); // Special case. Just return empty object + return instantiateWithSubtypeGuess(beanClass, emptySet()); // Special case. Just return empty object } Field valueField = XNodeProcessorUtil.findXmlValueField(beanClass); @@ -260,11 +262,26 @@ private T unmarshalFromMapOrHeteroList(@NotNull XNode mapOrList, @NotNull Cl throw new SchemaException("SearchFilterType is not supported in combination of heterogeneous list."); } } else { - T bean = instantiate(beanClass); + T bean = instantiateWithSubtypeGuess(beanClass, mapOrList); return unmarshalFromMapOrHeteroListToBean(bean, mapOrList, null, pc); } } + private T instantiateWithSubtypeGuess(@NotNull Class beanClass, XNode mapOrList) throws SchemaException { + if (!(mapOrList instanceof MapXNode)) { + return instantiate(beanClass); // guessing is supported only for traditional maps now + } + return instantiateWithSubtypeGuess(beanClass, ((MapXNode) mapOrList).keySet()); + } + + private T instantiateWithSubtypeGuess(@NotNull Class beanClass, Collection fields) throws SchemaException { + if (!Modifier.isAbstract(beanClass.getModifiers())) { + return instantiate(beanClass); // non-abstract classes are currently instantiated directly (could be changed) + } + Class subclass = inspector.findMatchingSubclass(beanClass, fields); + return instantiate(subclass); + } + private T instantiate(@NotNull Class beanClass) { T bean; try { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismBeanInspector.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismBeanInspector.java index 56a62bf7638..3378a3aea5d 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismBeanInspector.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/marshaller/PrismBeanInspector.java @@ -16,11 +16,15 @@ package com.evolveum.midpoint.prism.marshaller; +import com.evolveum.midpoint.prism.ComplexTypeDefinition; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.TypeDefinition; import com.evolveum.midpoint.prism.schema.PrismSchema; import com.evolveum.midpoint.prism.schema.SchemaDescription; +import com.evolveum.midpoint.prism.schema.SchemaRegistry; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.util.Handler; +import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.prism.xml.ns._public.types_3.RawType; import org.apache.commons.lang.StringUtils; @@ -40,11 +44,10 @@ import javax.xml.namespace.QName; import java.lang.reflect.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; + +import static java.util.Comparator.naturalOrder; +import static java.util.Comparator.nullsLast; /** * @author mederly @@ -81,17 +84,8 @@ interface Getter2 { } private V find2(final Map> cache, final P1 param1, final P2 param2, final Getter2 getter) { - Map cache2 = cache.get(param1); - if (cache2 == null) { - cache2 = Collections.synchronizedMap(new HashMap()); - cache.put(param1, cache2); - } - return find1(cache2, param2, new Getter1() { - @Override - public V get(P2 p) { - return getter.get(param1, p); - } - }); + Map cache2 = cache.computeIfAbsent(param1, k -> Collections.synchronizedMap(new HashMap<>())); + return find1(cache2, param2, p -> getter.get(param1, p)); } @FunctionalInterface @@ -100,108 +94,80 @@ interface Getter3 { } private V find3(final Map>> cache, final P1 param1, final P2 param2, final P3 param3, final Getter3 getter) { - Map> cache2 = cache.get(param1); - if (cache2 == null) { - cache2 = Collections.synchronizedMap(new HashMap()); - cache.put(param1, cache2); - } - return find2(cache2, param2, param3, new Getter2() { - @Override - public V get(P2 p, P3 q) { - return getter.get(param1, p, q); - } - }); + Map> cache2 = cache.computeIfAbsent(param1, k -> Collections.synchronizedMap(new HashMap<>())); + return find2(cache2, param2, param3, (p, q) -> getter.get(param1, p, q)); } //endregion //region Individual inspection methods - cached versions - private Map, String> _determineNamespace = Collections.synchronizedMap(new HashMap()); + private Map, String> _determineNamespace = Collections.synchronizedMap(new HashMap<>()); - String determineNamespace(Class paramType) { + String determineNamespace(Class paramType) { return find1(_determineNamespace, paramType, this::determineNamespaceUncached); } - private Map, QName> _determineTypeForClass = Collections.synchronizedMap(new HashMap()); + private Map, QName> _determineTypeForClass = Collections.synchronizedMap(new HashMap<>()); QName determineTypeForClass(Class paramType) { return find1(_determineTypeForClass, paramType, this::determineTypeForClassUncached); } - private Map> _isAttribute = Collections.synchronizedMap(new HashMap()); + private Map> _isAttribute = Collections.synchronizedMap(new HashMap<>()); boolean isAttribute(Field field, Method getter) { return find2(_isAttribute, field, getter, this::isAttributeUncached); } - private Map> _findSetter = Collections.synchronizedMap(new HashMap()); + private Map> _findSetter = Collections.synchronizedMap(new HashMap<>()); Method findSetter(Class beanClass, String fieldName) { - return find2(_findSetter, beanClass, fieldName, new Getter2() { - @Override - public Method get(Class c, String f) { - return findSetterUncached(c, f); - } - }); + //noinspection unchecked + return find2(_findSetter, beanClass, fieldName, (c, f) -> findSetterUncached(c, f)); } - private Map _getObjectFactoryClassPackage = Collections.synchronizedMap(new HashMap()); + private Map _getObjectFactoryClassPackage = Collections.synchronizedMap(new HashMap<>()); Class getObjectFactoryClass(Package aPackage) { - return find1(_getObjectFactoryClassPackage, aPackage, new Getter1() { - @Override - public Class get(Package p) { - return getObjectFactoryClassUncached(p); - } - }); + return find1(_getObjectFactoryClassPackage, aPackage, p -> getObjectFactoryClassUncached(p)); } - private Map _getObjectFactoryClassNamespace = Collections.synchronizedMap(new HashMap()); + private Map _getObjectFactoryClassNamespace = Collections.synchronizedMap(new HashMap<>()); Class getObjectFactoryClass(String namespaceUri) { - return find1(_getObjectFactoryClassNamespace, namespaceUri, new Getter1() { - @Override - public Class get(String s) { - return getObjectFactoryClassUncached(s); - } - }); + return find1(_getObjectFactoryClassNamespace, namespaceUri, s -> getObjectFactoryClassUncached(s)); } - private Map, List> _getPropOrder = Collections.synchronizedMap(new HashMap()); + private Map, List> _getPropOrder = Collections.synchronizedMap(new HashMap<>()); - List getPropOrder(Class beanClass) { + List getPropOrder(Class beanClass) { return find1(_getPropOrder, beanClass, this::getPropOrderUncached); } - private Map> _findElementMethodInObjectFactory = Collections.synchronizedMap(new HashMap()); + private Map> _findElementMethodInObjectFactory = Collections.synchronizedMap(new HashMap<>()); Method findElementMethodInObjectFactory(Class objectFactoryClass, String propName) { return find2(_findElementMethodInObjectFactory, objectFactoryClass, propName, (c, p) -> findElementMethodInObjectFactoryUncached(c, p)); } - private Map> _lookupSubstitution = Collections.synchronizedMap(new HashMap()); + private Map> _lookupSubstitution = Collections.synchronizedMap(new HashMap<>()); Field lookupSubstitution(Class beanClass, Method elementMethod) { return find2(_lookupSubstitution, beanClass, elementMethod, this::lookupSubstitutionUncached); } - private Map> _findEnumFieldName = Collections.synchronizedMap(new HashMap()); + private Map> _findEnumFieldName = Collections.synchronizedMap(new HashMap<>()); String findEnumFieldName(Class classType, String primValue) { return find2(_findEnumFieldName, classType, primValue, (c, v) -> findEnumFieldNameUncached(c, v)); } - private Map> _findEnumFieldValue = Collections.synchronizedMap(new HashMap()); + private Map> _findEnumFieldValue = Collections.synchronizedMap(new HashMap<>()); String findEnumFieldValue(Class classType, String toStringValue) { - return find2(_findEnumFieldValue, classType, toStringValue, new Getter2() { - @Override - public String get(Class c, String v) { - return findEnumFieldValueUncached(c, v); - } - }); + return find2(_findEnumFieldValue, classType, toStringValue, (c, v) -> findEnumFieldValueUncached(c, v)); } - private Map,Map>> _findTypeName = Collections.synchronizedMap(new HashMap()); + private Map,Map>> _findTypeName = Collections.synchronizedMap(new HashMap<>()); // Determines type for field/content combination. Field information is used only for simple XSD types. QName findTypeName(Field field, Class contentClass, String defaultNamespacePlaceholder) { @@ -209,24 +175,21 @@ QName findTypeName(Field field, Class contentClass, String defaultNamespacePl this::findTypeNameUncached); } - private Map,Map>> _findFieldElementQName = Collections.synchronizedMap(new HashMap()); + private Map,Map>> _findFieldElementQName = Collections.synchronizedMap(new HashMap<>()); - QName findFieldElementQName(String fieldName, Class beanClass, String defaultNamespace) { - return find3(_findFieldElementQName, fieldName, beanClass, defaultNamespace, new Getter3, String>() { - @Override - public QName get(String fieldName, Class beanClass, String defaultNamespace) { - return findFieldElementQNameUncached(fieldName, beanClass, defaultNamespace); - } - }); + QName findFieldElementQName(String fieldName, Class beanClass, String defaultNamespace) { + return find3(_findFieldElementQName, fieldName, beanClass, defaultNamespace, + (fieldName1, beanClass1, defaultNamespace1) -> findFieldElementQNameUncached(fieldName1, beanClass1, + defaultNamespace1)); } - private Map> _findPropertyGetter = Collections.synchronizedMap(new HashMap()); + private Map> _findPropertyGetter = Collections.synchronizedMap(new HashMap<>()); public Method findPropertyGetter(Class beanClass, String propName) { return find2(_findPropertyGetter, beanClass, propName, this::findPropertyGetterUncached); } - private Map> _findPropertyField = Collections.synchronizedMap(new HashMap()); + private Map> _findPropertyField = Collections.synchronizedMap(new HashMap<>()); public Field findPropertyField(Class beanClass, String propName) { return find2(_findPropertyField, beanClass, propName, this::findPropertyFieldUncached); @@ -247,11 +210,11 @@ private Field findPropertyFieldUncached(Class classType, String propName) private Field findPropertyFieldExactUncached(Class classType, String propName) { for (Field field: classType.getDeclaredFields()) { XmlElement xmlElement = field.getAnnotation(XmlElement.class); - if (xmlElement != null && xmlElement.name() != null && xmlElement.name().equals(propName)) { + if (xmlElement != null && xmlElement.name().equals(propName)) { return field; } XmlAttribute xmlAttribute = field.getAnnotation(XmlAttribute.class); - if (xmlAttribute != null && xmlAttribute.name() != null && xmlAttribute.name().equals(propName)) { + if (xmlAttribute != null && xmlAttribute.name().equals(propName)) { return field; } } @@ -300,23 +263,16 @@ private Method findPropertyGetterUncached(Class classType, String propNam return findPropertyGetter(superclass, propName); } - private boolean isAttributeUncached(Field field, Method getter){ - if (field == null && getter == null){ - return false; - } - - if (field != null && field.isAnnotationPresent(XmlAttribute.class)){ - return true; - } - - if (getter != null && getter.isAnnotationPresent(XmlAttribute.class)){ - return true; - } - - return false; + private boolean isAttributeUncached(Field field, Method getter) { + if (field == null && getter == null) { + return false; + } else { + return field != null && field.isAnnotationPresent(XmlAttribute.class) + || getter != null && getter.isAnnotationPresent(XmlAttribute.class); + } } - private String determineNamespaceUncached(Class beanClass) { + private String determineNamespaceUncached(Class beanClass) { XmlType xmlType = beanClass.getAnnotation(XmlType.class); if (xmlType == null) { return null; @@ -334,7 +290,7 @@ private String determineNamespaceUncached(Class beanClass) { return namespace; } - private QName determineTypeForClassUncached(Class beanClass) { + private QName determineTypeForClassUncached(Class beanClass) { XmlType xmlType = beanClass.getAnnotation(XmlType.class); if (xmlType == null) { return null; @@ -461,7 +417,7 @@ private Method findMethod(Class classType, Handler selector) { return findMethod(superclass, selector); } - private List getPropOrderUncached(Class beanClass) { + private List getPropOrderUncached(Class beanClass) { List propOrder; // Superclass first! @@ -581,11 +537,11 @@ private QName findFieldElementQNameUncached(String fieldName, Class beanClass, S XmlElement xmlElement = field.getAnnotation(XmlElement.class); if (xmlElement != null) { String name = xmlElement.name(); - if (name != null && !BeanMarshaller.DEFAULT_PLACEHOLDER.equals(name)) { + if (!BeanMarshaller.DEFAULT_PLACEHOLDER.equals(name)) { realLocalName = name; } String namespace = xmlElement.namespace(); - if (namespace != null && !BeanMarshaller.DEFAULT_PLACEHOLDER.equals(namespace)) { + if (!BeanMarshaller.DEFAULT_PLACEHOLDER.equals(namespace)) { realNamespace = namespace; } } @@ -593,6 +549,7 @@ private QName findFieldElementQNameUncached(String fieldName, Class beanClass, S } //endregion + //region Other public Field findAnyField(Class beanClass) { return findField(beanClass, field -> field.getAnnotation(XmlAnyElement.class) != null); } @@ -639,5 +596,50 @@ public Class getUpperBound(Type type, String desc) { } } + @NotNull + public Class findMatchingSubclass(Class beanClass, Collection fields) throws SchemaException { + SchemaRegistry schemaRegistry = prismContext.getSchemaRegistry(); + TypeDefinition typeDef = schemaRegistry.findTypeDefinitionByCompileTimeClass(beanClass, TypeDefinition.class); + if (typeDef == null) { + throw new SchemaException("No type definition for " + beanClass); + } + List subTypes = new ArrayList<>(typeDef.getStaticSubTypes()); + subTypes.sort(Comparator.comparing(TypeDefinition::getInstantiationOrder, nullsLast(naturalOrder()))); + TypeDefinition matchingDefinition = null; + for (TypeDefinition subType : subTypes) { + if (matchingDefinition != null && !Objects.equals(matchingDefinition.getInstantiationOrder(), subType.getInstantiationOrder())) { + break; // found something and went to lower orders -> we can stop searching + } + if (matches(subType, fields)) { + if (matchingDefinition != null) { + throw new SchemaException("Couldn't unambiguously determine a subclass for " + beanClass + + " instantiation (fields: " + fields + "). Candidates: " + matchingDefinition + ", " + subType); + } + matchingDefinition = subType; + } + } + if (matchingDefinition == null) { + final int MAX = 5; + throw new SchemaException("Couldn't find a subclass of " + beanClass + " that would contain fields " + fields + ". Considered " + + subTypes.subList(0, Math.min(subTypes.size(), MAX)) + + (subTypes.size() >= MAX ? " (...)" : "")); + } + //noinspection unchecked + Class compileTimeClass = (Class) matchingDefinition.getCompileTimeClass(); + if (compileTimeClass != null) { + return compileTimeClass; + } else { + throw new SchemaException("No compile time class defined for " + matchingDefinition); + } + } + + private boolean matches(TypeDefinition type, Collection fields) { + if (!(type instanceof ComplexTypeDefinition)) { + return false; + } + ComplexTypeDefinition ctd = (ComplexTypeDefinition) type; + return fields.stream().allMatch(field -> ctd.containsItemDefinition(field)); + } + //endregion } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/DomToSchemaPostProcessor.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/DomToSchemaPostProcessor.java index dfa08ebe7cc..129a642272a 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/DomToSchemaPostProcessor.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/DomToSchemaPostProcessor.java @@ -208,6 +208,8 @@ private ComplexTypeDefinition processComplexTypeDefinition(XSComplexType complex ctd.setSuperType(superType); } + setInstantiationOrder(ctd, complexType.getAnnotation()); + if (isObjectDefinition(complexType)) { ctd.setObjectMarker(true); } @@ -231,10 +233,8 @@ private ComplexTypeDefinition processComplexTypeDefinition(XSComplexType complex extractDocumentation(ctd, complexType.getAnnotation()); - if (getSchemaRegistry() != null) { - Class compileTimeClass = getSchemaRegistry().determineCompileTimeClass(ctd.getTypeName()); - ctd.setCompileTimeClass(compileTimeClass); - } + Class compileTimeClass = getSchemaRegistry().determineCompileTimeClass(ctd.getTypeName()); + ctd.setCompileTimeClass(compileTimeClass); definitionFactory.finishComplexTypeDefinition(ctd, complexType, prismContext, complexType.getAnnotation()); @@ -255,6 +255,11 @@ private ComplexTypeDefinition processComplexTypeDefinition(XSComplexType complex } + private void setInstantiationOrder(TypeDefinitionImpl typeDefinition, XSAnnotation annotation) throws SchemaException { + Integer order = SchemaProcessorUtil.getAnnotationInteger(annotation, A_INSTANTIATION_ORDER); + typeDefinition.setInstantiationOrder(order); + } + private void processSimpleTypeDefinitions(XSSchemaSet set) throws SchemaException { Iterator iterator = set.iterateSimpleTypes(); while (iterator.hasNext()) { @@ -285,6 +290,8 @@ private SimpleTypeDefinition processSimpleTypeDefinition(XSSimpleType simpleType std.setSuperType(superType); } + setInstantiationOrder(std, simpleType.getAnnotation()); + extractDocumentation(std, simpleType.getAnnotation()); if (getSchemaRegistry() != null) { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaProcessorUtil.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaProcessorUtil.java index 5ca7757dacb..ed0f314595d 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaProcessorUtil.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaProcessorUtil.java @@ -109,7 +109,7 @@ public static Boolean getAnnotationBooleanMarker(XSAnnotation annotation, QName return XmlTypeConverter.toJavaValue(element, Boolean.class); } - public static Boolean getAnnotationBoolean(XSAnnotation annotation, QName qname) throws SchemaException { + public static T getAnnotationConverted(XSAnnotation annotation, QName qname, Class type) throws SchemaException { Element element = getAnnotationElement(annotation, qname); if (element == null) { return null; @@ -118,7 +118,15 @@ public static Boolean getAnnotationBoolean(XSAnnotation annotation, QName qname) if (textContent == null || textContent.isEmpty()) { return null; } - return XmlTypeConverter.toJavaValue(element, Boolean.class); + return XmlTypeConverter.toJavaValue(element, type); + } + + public static Boolean getAnnotationBoolean(XSAnnotation annotation, QName qname) throws SchemaException { + return getAnnotationConverted(annotation, qname, Boolean.class); + } + + public static Integer getAnnotationInteger(XSAnnotation annotation, QName qname) throws SchemaException { + return getAnnotationConverted(annotation, qname, Integer.class); } public static Integer parseMultiplicity(String stringMultiplicity) { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistryImpl.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistryImpl.java index 9c4f01d7450..0a5505dcf2c 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistryImpl.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistryImpl.java @@ -410,7 +410,9 @@ private void parsePrismSchemas() throws SchemaException { applySchemaExtensions(); for (SchemaDescription schemaDescription : schemaDescriptions) { if (schemaDescription.getSchema() != null) { - resolveMissingTypeDefinitionsInGlobalItemDefinitions((PrismSchemaImpl) schemaDescription.getSchema()); + PrismSchemaImpl schema = (PrismSchemaImpl) schemaDescription.getSchema(); + resolveMissingTypeDefinitionsInGlobalItemDefinitions(schema); + fillInSubtypes(schema); } } if (LOGGER.isTraceEnabled()) { @@ -425,6 +427,18 @@ private void parsePrismSchemas() throws SchemaException { } } + private void fillInSubtypes(PrismSchemaImpl schema) { + for (TypeDefinition typeDefinition : schema.getDefinitions(TypeDefinition.class)) { + if (typeDefinition.getSuperType() == null) { + continue; + } + TypeDefinition superTypeDef = findTypeDefinitionByType(typeDefinition.getSuperType(), TypeDefinition.class); + if (superTypeDef instanceof TypeDefinitionImpl) { + ((TypeDefinitionImpl) superTypeDef).addStaticSubType(typeDefinition); + } + } + } + // global item definitions may refer to types that are not yet available private void resolveMissingTypeDefinitionsInGlobalItemDefinitions(PrismSchemaImpl schema) throws SchemaException { for (Iterator iterator = schema.getDelayedItemDefinitions().iterator(); iterator.hasNext(); ) { diff --git a/infra/prism/src/main/resources/xml/ns/public/annotation-3.xsd b/infra/prism/src/main/resources/xml/ns/public/annotation-3.xsd index f6cc8e0fbe2..95e0da3fb1a 100644 --- a/infra/prism/src/main/resources/xml/ns/public/annotation-3.xsd +++ b/infra/prism/src/main/resources/xml/ns/public/annotation-3.xsd @@ -434,6 +434,19 @@ + + + + When parsing a field that is known to contain an abstract data type (e.g. LocalizableMessageType) + and has no xsi:type, one has to guess which subtype to instantiate. Candidate subtypes can be derived + when looking at sub-items. However, if there are more candidates, one of them must be selected. + This is done using instantiationOrder annotation. + + EXPERIMENTAL. Maybe this is really a wrong idea, so be prepared it could be removed. + + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index a55475baa9f..31dd883d44d 100755 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -18185,6 +18185,9 @@ 3.7 + + 1 diff --git a/infra/schema/src/test/resources/common/xml/ns/wf-context.xml b/infra/schema/src/test/resources/common/xml/ns/wf-context.xml index 629b2acf12d..0dbb3725ba4 100644 --- a/infra/schema/src/test/resources/common/xml/ns/wf-context.xml +++ b/infra/schema/src/test/resources/common/xml/ns/wf-context.xml @@ -22,8 +22,7 @@ 1 a b - + com.evolveum.midpoint.wf.impl.processors.primary.policy.PolicyRuleBasedAspect @@ -43,6 +42,24 @@ + + + + test + + + + + + + + testkey + + + + + + 2017-02-03T15:59:40.015+01:00 diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java index 1d3f7b6a635..a89a7315174 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java @@ -60,6 +60,7 @@ /** * @author mederly */ +@SuppressWarnings("unused") public interface MidpointFunctions { /** @@ -167,12 +168,12 @@ T resolveReferenceIfExists(ObjectReferenceType reference) * requested object does not exist * @throws SchemaException * the object is not schema compliant - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw ConfigurationException + * @throws ConfigurationException * Configuration error. E.g. misconfigured resource parameters, invalid policies, etc. * @throws IllegalArgumentException * missing required parameter, wrong OID format, etc. @@ -205,12 +206,12 @@ T getObject(Class type, String oid, Collection T getObject(Class type, String oid, Collection T getObject(Class type, String oid) - throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException; + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException; /** *

@@ -285,7 +286,7 @@ T getObject(Class type, String oid) * Configuration error. E.g. misconfigured resource parameters, invalid policies, etc. * @throws PolicyViolationException * Policy violation was detected during processing of the object - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws IllegalArgumentException @@ -356,7 +357,7 @@ void executeChanges(Collection> deltas, ModelE * Configuration error. E.g. misconfigured resource parameters, invalid policies, etc. * @throws PolicyViolationException * Policy violation was detected during processing of the object - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws IllegalArgumentException @@ -427,7 +428,7 @@ void executeChanges(Collection> deltas) * Configuration error. E.g. misconfigured resource parameters, invalid policies, etc. * @throws PolicyViolationException * Policy violation was detected during processing of the object - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws IllegalArgumentException @@ -435,7 +436,8 @@ void executeChanges(Collection> deltas) * @throws SystemException * unknown error from underlying layers or other unexpected state */ - void executeChanges(ObjectDelta... deltas) + @SuppressWarnings("unchecked") + void executeChanges(ObjectDelta... deltas) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException; @@ -500,9 +502,12 @@ void recompute(Class type, String oid) * @return owner of the account or null * @throws ObjectNotFoundException * specified account was not found - * @throws SchemaException - * @throws SecurityViolationException - * @throws CommunicationException + * @throws SchemaException + * todo + * @throws SecurityViolationException + * todo + * @throws CommunicationException + * todo * @throws IllegalArgumentException * wrong OID format, described change is not applicable * @throws SystemException @@ -547,7 +552,7 @@ void recompute(Class type, String oid) * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -593,7 +598,7 @@ List searchObjects(Class type, ObjectQuery query, * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -631,7 +636,7 @@ List searchObjects(Class type, ObjectQuery query) t * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -667,7 +672,7 @@ void searchObjectsIterative(Class type, ObjectQuery qu * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -701,7 +706,7 @@ void searchObjectsIterative(Class type, ObjectQuery qu * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -735,7 +740,7 @@ T searchObjectByName(Class type, String name) throws S * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -769,7 +774,7 @@ T searchObjectByName(Class type, PolyString name) thro * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -805,7 +810,7 @@ T searchObjectByName(Class type, PolyStringType name) * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -839,7 +844,7 @@ int countObjects(Class type, ObjectQuery query, Collec * object required for a search was not found (e.g. resource definition) * @throws CommunicationException * Communication (network) error during retrieval. E.g. error communicating with the resource - * @throw SecurityViolationException + * @throws SecurityViolationException * Security violation during operation execution. May be caused either by midPoint internal * security mechanism but also by external mechanism (e.g. on the resource) * @throws ConfigurationException @@ -972,7 +977,7 @@ Collection getManagersOidsExceptUser(@NotNull Collection getManagersOfOrg(String orgOid) throws SchemaException, SecurityViolationException; /** - * Returns true if user is a manager of specified organiational unit. + * Returns true if user is a manager of specified organizational unit. */ boolean isManagerOf(UserType user, String orgOid); @@ -1023,7 +1028,6 @@ Collection getManagersOidsExceptUser(@NotNull Collection getManagersOidsExceptUser(@NotNull Collection parseXmlToMap(String xml); From 61a9d65a6722c7b8845c3b686a99111c2456e858 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 5 Dec 2017 12:47:46 +0100 Subject: [PATCH 3/4] Removed (now unnecessary) support for multiple localizable messages in exceptions. --- .../midpoint/common/LocalizationService.java | 4 ++++ .../util/LocalizableMessageListBuilder.java | 8 +++++++ .../util/exception/CommonException.java | 18 +--------------- .../exception/PolicyViolationException.java | 7 ------- .../ObjectValuePolicyEvaluator.java | 11 +++++----- .../stringpolicy/ValuePolicyProcessor.java | 7 ++++++- .../impl/hooks/PolicyRuleEnforcerHook.java | 21 +++++++------------ 7 files changed, 32 insertions(+), 44 deletions(-) diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationService.java b/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationService.java index 4df08f399e4..0137595a779 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationService.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationService.java @@ -30,4 +30,8 @@ public interface LocalizationService { String translate(String key, Object[] params, Locale locale, String defaultMessage); String translate(LocalizableMessage msg, Locale locale); + + default String translate(LocalizableMessage msg) { + return translate(msg, Locale.getDefault()); + } } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java index 169173e6ac5..9deec5cb7ac 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/LocalizableMessageListBuilder.java @@ -62,4 +62,12 @@ public LocalizableMessageList build() { return new LocalizableMessageList(messages, separator, prefix, postfix); } + // beware, ignores prefix and postfix for singleton lists + public LocalizableMessage buildOptimized() { + if (messages.size() == 1) { + return messages.get(0); + } else { + return build(); + } + } } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/exception/CommonException.java b/infra/util/src/main/java/com/evolveum/midpoint/util/exception/CommonException.java index 8fcf8469d2a..37ad662e647 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/exception/CommonException.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/exception/CommonException.java @@ -16,13 +16,6 @@ package com.evolveum.midpoint.util.exception; import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.ShortDumpable; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; /** * Superclass for all common midPoint exceptions. @@ -33,7 +26,6 @@ public abstract class CommonException extends Exception { LocalizableMessage userFriendlyMessage; - List otherUserFriendlyMessages; public CommonException() { } @@ -70,8 +62,6 @@ public CommonException(String message, Throwable cause, LocalizableMessage userF * that the exception represents. E.g. "Communication error", "Policy violation", etc. * * TOTO: switch return value to a localized message - * - * @return */ public abstract String getErrorTypeMessage(); @@ -93,13 +83,7 @@ public String toString() { if (userFriendlyMessage == null) { return super.toString(); } else { - return super.toString() + - Stream.concat(Stream.of(userFriendlyMessage), emptyIfNull(otherUserFriendlyMessages).stream()) - .map(ShortDumpable::shortDump) - .collect(Collectors.joining("; ", " [", "]")); + return super.toString() + " [" + userFriendlyMessage.shortDump() + "]"; } } - - - } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/exception/PolicyViolationException.java b/infra/util/src/main/java/com/evolveum/midpoint/util/exception/PolicyViolationException.java index b7e89adcd49..b9156f026c1 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/exception/PolicyViolationException.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/exception/PolicyViolationException.java @@ -37,13 +37,6 @@ public PolicyViolationException(LocalizableMessage userFriendlyMessage) { super(userFriendlyMessage); } - // TEMPORARY - // requires at least one message - public PolicyViolationException(String message, List userFriendlyMessages) { - super(message, null, userFriendlyMessages.get(0)); - otherUserFriendlyMessages = userFriendlyMessages.subList(1, userFriendlyMessages.size()); - } - public PolicyViolationException(Throwable cause) { super(cause); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java index 194e212b819..8ae92c605da 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ObjectValuePolicyEvaluator.java @@ -36,10 +36,7 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.LocalizableMessageBuilder; -import com.evolveum.midpoint.util.LocalizableMessageList; -import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.*; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -199,7 +196,11 @@ public OperationResult validateStringValue(String clearValue) throws SchemaExcep result.computeStatus(); if (!result.isSuccess() && !messages.isEmpty()) { - result.setUserFriendlyMessage(new LocalizableMessageList(messages, LocalizableMessageList.SPACE, null, null)); + result.setUserFriendlyMessage( + new LocalizableMessageListBuilder() + .messages(messages) + .separator(LocalizableMessageList.SPACE) + .buildOptimized()); } return result; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java index 881da0b1ed8..fa682afeaab 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/stringpolicy/ValuePolicyProcessor.java @@ -30,6 +30,7 @@ import com.evolveum.midpoint.util.LocalizableMessage; import com.evolveum.midpoint.util.LocalizableMessageBuilder; import com.evolveum.midpoint.util.LocalizableMessageList; +import com.evolveum.midpoint.util.LocalizableMessageListBuilder; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; @@ -221,7 +222,11 @@ public boolean validateValue(String newValue, ValuePolicy result.computeStatus(); if (!result.isSuccess() && !messages.isEmpty()) { - result.setUserFriendlyMessage(new LocalizableMessageList(messages, LocalizableMessageList.SPACE, null, null)); + result.setUserFriendlyMessage( + new LocalizableMessageListBuilder() + .messages(messages) + .separator(LocalizableMessageList.SPACE) + .buildOptimized()); } return result.isAcceptable(); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/hooks/PolicyRuleEnforcerHook.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/hooks/PolicyRuleEnforcerHook.java index f0dfeb8ff27..23015ec7cda 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/hooks/PolicyRuleEnforcerHook.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/hooks/PolicyRuleEnforcerHook.java @@ -28,6 +28,8 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.LocalizableMessageList; +import com.evolveum.midpoint.util.LocalizableMessageListBuilder; import com.evolveum.midpoint.util.TreeNode; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.logging.Trace; @@ -81,23 +83,14 @@ public HookOperationMode invoke(@NotNull ModelContext if (context.getState() == ModelState.PRIMARY) { EvaluationContext evalCtx = invokeInternal(context, task, result); if (!evalCtx.messages.isEmpty()) { - throw new PolicyViolationException(translateMessages(evalCtx.messages), evalCtx.messages); + LocalizableMessage message = new LocalizableMessageListBuilder() + .messages(evalCtx.messages) + .separator(LocalizableMessageList.SEMICOLON) + .buildOptimized(); + throw new PolicyViolationException(message); } } return HookOperationMode.FOREGROUND; - - } - - @NotNull - private String translateMessages(List messages) { - StringBuilder sb = new StringBuilder(); - for (LocalizableMessage message : messages) { - if (sb.length() > 0) { - sb.append("; "); - } - sb.append(localizationService.translate(message, Locale.getDefault())); - } - return sb.toString(); } @Override From 489dc9b10a92e4f16cd0e073e6e87aad6acf6b39 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 5 Dec 2017 12:56:18 +0100 Subject: [PATCH 4/4] Compilation fix. --- .../model/impl/util/AbstractSearchIterativeResultHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeResultHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeResultHandler.java index 88b85914ba4..fe6d6f63499 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeResultHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeResultHandler.java @@ -504,7 +504,7 @@ public void createWorkerThreads(Task coordinatorTask, OperationResult opResult) subtask.resetActionsExecutedInformation(null); } subtask.setCategory(coordinatorTask.getCategory()); - subtask.setResult(new OperationResult(taskOperationPrefix + ".executeWorker", OperationResultStatus.IN_PROGRESS, null)); + subtask.setResult(new OperationResult(taskOperationPrefix + ".executeWorker", OperationResultStatus.IN_PROGRESS, (String) null)); subtask.setName("Worker thread " + (i+1) + " of " + threadsCount); subtask.startLightweightHandler(); LOGGER.trace("Worker subtask {} created", subtask);