package rocks.inspectit.shared.cs.cmr.property.configuration; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlSeeAlso; import org.apache.commons.collections.CollectionUtils; import rocks.inspectit.shared.cs.cmr.property.configuration.impl.BooleanProperty; import rocks.inspectit.shared.cs.cmr.property.configuration.impl.ByteProperty; import rocks.inspectit.shared.cs.cmr.property.configuration.impl.LongProperty; import rocks.inspectit.shared.cs.cmr.property.configuration.impl.PercentageProperty; import rocks.inspectit.shared.cs.cmr.property.configuration.impl.StringProperty; import rocks.inspectit.shared.cs.cmr.property.configuration.validation.PropertyValidation; import rocks.inspectit.shared.cs.cmr.property.configuration.validation.PropertyValidationException; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.ISinglePropertyValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.EMailListValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.EMailValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.FullyQualifiedClassNameValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.GreaterOrEqualValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.GreaterValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.LessOrEqualValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.LessValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.NegativeValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.NotEmptyValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.PercentageValidator; import rocks.inspectit.shared.cs.cmr.property.configuration.validator.impl.PositiveValidator; import rocks.inspectit.shared.cs.cmr.property.update.AbstractPropertyUpdate; import rocks.inspectit.shared.cs.cmr.property.update.IPropertyUpdate; import rocks.inspectit.shared.cs.cmr.property.update.impl.RestoreDefaultPropertyUpdate; /** * Single property denotes one concrete configuration in the CMR with logical name and value. * * @author Ivan Senic * * @param <T> * Type of the value property is defining. */ @XmlAccessorType(XmlAccessType.FIELD) @XmlSeeAlso({ StringProperty.class, LongProperty.class, BooleanProperty.class, PercentageProperty.class, ByteProperty.class }) public abstract class SingleProperty<T> extends AbstractProperty { /** * The logical name of the property that is used in the configuration. */ @XmlAttribute(name = "logical-name", required = true) private String logicalName; /** * If the property is advanced, thus should be available only to expert users. */ @XmlAttribute(name = "advanced", required = true) private boolean advanced; /** * If the change of this property should trigger server restart. */ @XmlAttribute(name = "server-restart-required", required = true) private boolean serverRestartRequired; /** * Validators used for this property. */ @XmlElementWrapper(name = "validators") @XmlElementRefs({ @XmlElementRef(type = LessValidator.class), @XmlElementRef(type = LessOrEqualValidator.class), @XmlElementRef(type = GreaterValidator.class), @XmlElementRef(type = GreaterOrEqualValidator.class), @XmlElementRef(type = PercentageValidator.class), @XmlElementRef(type = PositiveValidator.class), @XmlElementRef(type = NegativeValidator.class), @XmlElementRef(type = FullyQualifiedClassNameValidator.class), @XmlElementRef(type = NotEmptyValidator.class), @XmlElementRef(type = EMailValidator.class), @XmlElementRef(type = EMailListValidator.class) }) private List<ISinglePropertyValidator<? super T>> validators; /** * No-arg constructor. */ public SingleProperty() { } /** * * @param name * Display name of the property. Can not be <code>null</code>. * @param description * Description providing more information on property. * @param logicalName * The logical name of the property that is used in the configuration. * @param defaultValue * Default value. * @param advanced * If the property is advanced, thus should be available only to expert users. * @param serverRestartRequired * If the change of this property should trigger server restart. * @throws IllegalArgumentException * If name, section, logical name or default value are <code>null</code>. * @see {@link AbstractProperty#AbstractProperty(String, String)} */ public SingleProperty(String name, String description, String logicalName, T defaultValue, boolean advanced, boolean serverRestartRequired) throws IllegalArgumentException { super(name, description); if (null == logicalName) { throw new IllegalArgumentException("Logical name of the property can not be null."); } if (null == defaultValue) { throw new IllegalArgumentException("Default value of the property can not be null."); } this.logicalName = logicalName; this.advanced = advanced; this.serverRestartRequired = serverRestartRequired; this.setDefaultValue(defaultValue); } /** * Gets the default value. * * @return Gets the default value. */ public abstract T getDefaultValue(); /** * Sets the default value. * * @param defaultValue * New value for the default value. */ protected abstract void setDefaultValue(T defaultValue); /** * Gets the currently used value. * * @return Gets the currently used value. */ protected abstract T getUsedValue(); /** * Sets the currently used value. * * @param usedValue * New value for the currently used value. */ protected abstract void setUsedValue(T usedValue); /** * Parses the given string literal to the correct type of the property. <br> * Needed for property validation against literals. * * @param literal * String to parse. * @return Object of the value type or <code>null</code> is parsing can not be done. */ public abstract T parseLiteral(String literal); /** * Creates a {@link AbstractPropertyUpdate} of this property with the given update value. * * @param updateValue * Update value. * @return {@link AbstractPropertyUpdate}. */ protected abstract AbstractPropertyUpdate<T> createPropertyUpdate(T updateValue); /** * /** {@inheritDoc} */ @Override public boolean isAdvanced() { return advanced; } /** * {@inheritDoc} */ @Override public boolean isServerRestartRequired() { return serverRestartRequired; } /** * {@inheritDoc} */ @Override protected void validate(PropertyValidation propertyValidation) { if (CollectionUtils.isNotEmpty(validators)) { for (ISinglePropertyValidator<? super T> validator : validators) { validator.validate(this, propertyValidation); } } } /** * Check if the property would have validation errors if given value would be set. * * @param value * Value to check for. * @return {@link PropertyValidation} with errors if ones exist. */ protected PropertyValidation validateForValue(T value) { PropertyValidation propertyValidation = PropertyValidation.createFor(this); if (CollectionUtils.isNotEmpty(validators)) { for (ISinglePropertyValidator<? super T> validator : validators) { validator.validateForValue(this, propertyValidation, value); } } return propertyValidation; } /** * {@inheritDoc} */ @Override public void register(Properties properties) { properties.setProperty(logicalName, getValue().toString()); } /** * {@inheritDoc} */ @Override public SingleProperty<?> forLogicalname(String propertyLogicalName) { if (logicalName.equals(propertyLogicalName)) { return this; } else { return null; } } /** * Sets this property to use default value. */ public void setToDefaultValue() { setUsedValue(null); } /** * Validates the update value against the property and if validation passes returns the * {@link AbstractPropertyUpdate} object. * * @param updateValue * Update value * @return {@link AbstractPropertyUpdate} * @throws PropertyValidationException * If validation fails. */ public AbstractPropertyUpdate<T> createAndValidatePropertyUpdate(T updateValue) throws PropertyValidationException { if (getValue().equals(updateValue)) { throw new PropertyValidationException("Update value for creating property update can not be same as current property value."); } PropertyValidation propertyValidation = this.validateForValue(updateValue); // if has errors raise exception, otherwise create property update if (propertyValidation.hasErrors()) { throw new PropertyValidationException(propertyValidation); } else { return this.createPropertyUpdate(updateValue); } } /** * Creates a {@link RestoreDefaultPropertyUpdate} for this property. * * @return {@link RestoreDefaultPropertyUpdate} */ public RestoreDefaultPropertyUpdate<T> createRestoreDefaultPropertyUpdate() { return new RestoreDefaultPropertyUpdate<>(this); } /** * Checks if the update of the property can be done with the given * {@link AbstractPropertyUpdate}. For the property to be update-able following must be met: * <ul> * <li>Logical name of this property must match the logical name in the * {@link AbstractPropertyUpdate} * <li>Value and update value must be of same class * <li>Value and update value must not be equal * <li>Update value must pass all validations * </ul> * * @param propertyUpdate * {@link AbstractPropertyUpdate}. * @return True if this property can be update with given {@link AbstractPropertyUpdate}. */ @SuppressWarnings("unchecked") public boolean canUpdate(IPropertyUpdate<?> propertyUpdate) { if (propertyUpdate.isRestoreDefault()) { return true; } if (!logicalName.equals(propertyUpdate.getPropertyLogicalName())) { return false; } Object updateValue = propertyUpdate.getUpdateValue(); if (!getValue().getClass().equals(updateValue.getClass())) { return false; } if (getValue().equals(updateValue)) { return false; } PropertyValidation propertyValidation = this.validateForValue((T) updateValue); return !propertyValidation.hasErrors(); } /** * Adds {@link ISinglePropertyValidator} to be used for validating this property value. * * @param validator * Validator. */ public void addValidator(ISinglePropertyValidator<? super T> validator) { if (null == validators) { validators = new ArrayList<>(); } validators.add(validator); } /** * If default value is used for this property. * * @return If default value is used for this property. */ public boolean isDefaultValueUsed() { if (null != getUsedValue()) { return getUsedValue().equals(getDefaultValue()); } else { return true; } } /** * Gets {@link #value}. * * @return {@link #value} */ public T getValue() { if (null != getUsedValue()) { return getUsedValue(); } else { return getDefaultValue(); } } /** * Sets {@link #value}. * * @param value * New value for {@link #value} */ public void setValue(T value) { setUsedValue(value); } /** * Gets {@link #logicalName}. * * @return {@link #logicalName} */ public String getLogicalName() { return logicalName; } /** * Returns property value as human-readable string . * * @return Returns property value as human-readable string . */ public String getFormattedValue() { return getValue().toString(); } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = (prime * result) + ((getDefaultValue() == null) ? 0 : getDefaultValue().hashCode()); result = (prime * result) + ((logicalName == null) ? 0 : logicalName.hashCode()); result = (prime * result) + ((getUsedValue() == null) ? 0 : getUsedValue().hashCode()); return result; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } SingleProperty<?> other = (SingleProperty<?>) obj; if (getDefaultValue() == null) { if (other.getDefaultValue() != null) { return false; } } else if (!getDefaultValue().equals(other.getDefaultValue())) { return false; } if (logicalName == null) { if (other.logicalName != null) { return false; } } else if (!logicalName.equals(other.logicalName)) { return false; } if (getUsedValue() == null) { if (other.getUsedValue() != null) { return false; } } else if (!getUsedValue().equals(other.getUsedValue())) { return false; } return true; } }