/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sling.validation.impl;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.sling.validation.impl.util.ValidatorTypeUtil;
import org.apache.sling.validation.spi.Validator;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class which encapsulates a map of {@link Validator}s with their meta information.
*
*/
public class ValidatorMap {
final static class ValidatorMetadata implements Comparable<ValidatorMetadata> {
protected final @Nonnull Validator<?> validator;
// default severity of the validator
protected final Integer severity;
protected final @Nonnull Class<?> type;
/** used for comparison, to sort by service ranking and id */
protected final @Nonnull ServiceReference<Validator<?>> serviceReference;
public ValidatorMetadata(Validator<?> validator, ServiceReference<Validator<?>> serviceReference, Integer severity) {
this.validator = validator;
this.severity = severity;
this.serviceReference = serviceReference;
// cache validator's type (as this is using reflection)
type = ValidatorTypeUtil.getValidatorType(validator);
}
@Override
public int compareTo(ValidatorMetadata o) {
return serviceReference.compareTo(o.serviceReference);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((serviceReference == null) ? 0 : serviceReference.hashCode());
result = prime * result + ((severity == null) ? 0 : severity.hashCode());
result = prime * result + ((validator == null) ? 0 : validator.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ValidatorMetadata other = (ValidatorMetadata) obj;
if (serviceReference == null) {
if (other.serviceReference != null)
return false;
} else if (!serviceReference.equals(other.serviceReference))
return false;
if (severity == null) {
if (other.severity != null)
return false;
} else if (!severity.equals(other.severity))
return false;
if (validator == null) {
if (other.validator != null)
return false;
} else if (!validator.equals(other.validator))
return false;
return true;
}
@Override
public String toString() {
return "Entry [validator=" + validator + ", severity=" + severity + ", type=" + type + ", from bundle '" + serviceReference.getBundle().getSymbolicName() + "'"
+ "]";
}
public @Nonnull Validator<?> getValidator() {
return validator;
}
public @CheckForNull Integer getSeverity() {
return severity;
}
public @Nonnull Class<?> getType() {
return type;
}
}
private static final Logger LOG = LoggerFactory.getLogger(ValidatorMap.class);
private final Map<String, ValidatorMetadata> validatorMap;
public ValidatorMap() {
validatorMap = new ConcurrentHashMap<>();
}
private String getValidatorIdFromServiceProperties(Map<String, Object> properties, @SuppressWarnings("rawtypes") Class<? extends Validator> validatorClass,
ServiceReference<Validator<?>> serviceReference) {
Object id = properties.get(Validator.PROPERTY_VALIDATOR_ID);
if (id == null) {
throw new IllegalArgumentException("Validator '" + validatorClass.getName() + "' provided from bundle "
+ serviceReference.getBundle().getBundleId() +
" is lacking the mandatory service property " + Validator.PROPERTY_VALIDATOR_ID);
}
if (!(id instanceof String)) {
throw new IllegalArgumentException("Validator '" + validatorClass.getName() + "' provided from bundle "
+ serviceReference.getBundle().getBundleId() +
" is providing the mandatory service property " + Validator.PROPERTY_VALIDATOR_ID + " with the wrong type "
+ id.getClass() + " (must be of type String)");
}
return (String) id;
}
private Integer getValidatorSeverityFromServiceProperties(Map<String, Object> properties, Validator<?> validator,
ServiceReference<Validator<?>> serviceReference) {
Object severity = properties.get(Validator.PROPERTY_VALIDATOR_SEVERITY);
if (severity == null) {
LOG.debug("Validator '{}' is not setting an explicit severity via the OSGi service property {}", validator.getClass().getName(), Validator.PROPERTY_VALIDATOR_SEVERITY);
return null;
}
if (!(severity instanceof Integer)) {
throw new IllegalArgumentException("Validator '" + validator.getClass().getName() + "' provided from bundle "
+ serviceReference.getBundle().getBundleId() +
" is providing the optional service property " + Validator.PROPERTY_VALIDATOR_SEVERITY + " with the wrong type "
+ severity.getClass() + " (must be of type Integer)");
}
return (Integer) severity;
}
public void put(@Nonnull Map<String, Object> properties, @Nonnull Validator<?> validator, ServiceReference<Validator<?>> serviceReference) {
String validatorId = getValidatorIdFromServiceProperties(properties, validator.getClass(), serviceReference);
Integer severity = getValidatorSeverityFromServiceProperties(properties, validator, serviceReference);
put(validatorId, validator, serviceReference, severity);
}
void put(@Nonnull String id, @Nonnull Validator<?> validator, ServiceReference<Validator<?>> serviceReference, Integer severity) {
// create new entry
ValidatorMetadata entry = new ValidatorMetadata(validator, serviceReference, severity);
if (validatorMap.containsKey(id)) {
ValidatorMetadata existingEntry = validatorMap.get(id);
if (entry.compareTo(existingEntry) > 0) {
LOG.info("Overwriting already existing validator {} with {}, because it has the same id '{}' and a higher service ranking",
existingEntry, entry, id);
} else {
LOG.info(
"A Validator for the same id '{}' is already registered {} and it has a higher service ranking, therefore ignoring {}",
id, existingEntry, entry);
return;
}
} else {
LOG.debug("New validator with id '{}' added: {}", id, entry);
}
validatorMap.put(id, entry);
}
public void update(@Nonnull Map<String, Object> properties, @Nonnull Validator<?> validator, ServiceReference<Validator<?>> serviceReference) {
String validatorId = getValidatorIdFromServiceProperties(properties, validator.getClass(), serviceReference);
Integer severity = getValidatorSeverityFromServiceProperties(properties, validator, serviceReference);
update(validatorId, validator, serviceReference, severity);
}
void update(@Nonnull String id, @Nonnull Validator<?> validator, ServiceReference<Validator<?>> serviceReference, Integer severity) {
LOG.info("Updating validator with id '{}'", id);
// the id might have been changed, therefore remove old entry by looking up the service reference!
remove(serviceReference);
put(id, validator, serviceReference, severity);
}
private boolean remove(ServiceReference<Validator<?>> serviceReference) {
for (java.util.Iterator<ValidatorMetadata> iterator = validatorMap.values().iterator(); iterator.hasNext();) {
ValidatorMetadata value = iterator.next();
if (value.serviceReference.equals(serviceReference)) {
iterator.remove();
return true;
}
}
return false;
}
public boolean remove(@Nonnull Map<String, Object> properties, @Nonnull Validator<?> validator, ServiceReference<Validator<?>> serviceReference) {
String validatorId = getValidatorIdFromServiceProperties(properties, validator.getClass(), serviceReference);
return remove(validatorId, serviceReference);
}
public boolean remove(String id, ServiceReference<Validator<?>> serviceReference) {
// only actually remove if the service reference is equal
if (id == null) {
// find by service reference
}
ValidatorMetadata entry = validatorMap.get(id);
if (entry == null) {
LOG.warn("Could not remove validator with id '{}' from map, because it is not there!", id);
return false;
} else {
// only actually remove if the service reference is equal
if (entry.serviceReference.equals(serviceReference)) {
return true;
} else {
LOG.warn("Could not remove validator with id '{}' from map because it is only contained with a different service reference!", id);
return false;
}
}
}
public ValidatorMetadata get(String id) {
return validatorMap.get(id);
}
}