/*
* 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.cyclop.validation;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import javax.validation.ConstraintViolation;
import javax.validation.Path;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.metadata.ConstraintDescriptor;
import org.cyclop.model.Synchronizable;
import org.cyclop.model.exception.BeanValidationException;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
/** @author Maciej Miklas */
public final class BeanValidator {
private final static String NULL_MARKER = "NULL-" + UUID.randomUUID();
private final static Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
private Map<String, Object> objMap = new HashMap<>();
public static BeanValidator create() {
return new BeanValidator();
}
public static BeanValidator create(Object obj) {
return new BeanValidator().add(obj);
}
public BeanValidator add(Object obj) {
if (obj == null) {
throw new IllegalArgumentException("null validation object");
}
return add(obj.getClass().getSimpleName(), obj);
}
public BeanValidator add(String name, Object obj) {
if (name == null) {
throw new IllegalArgumentException("null name for validation object");
}
String key = objMap.containsKey(name) ? name + " - " + UUID.randomUUID() : name;
objMap.put(key, obj == null ? NULL_MARKER : obj);
return this;
}
public void validate() {
Map<String, Set<ConstraintViolation<Object>>> violations = new HashMap<>();
for (Map.Entry<String, Object> obj : objMap.entrySet()) {
Object value = obj.getValue();
if (value.toString().equals(NULL_MARKER)) {
violations.put(obj.getKey(), createViolationForNullRoot());
continue;
}
Set<ConstraintViolation<Object>> violation = validateObject(value);
if (!violation.isEmpty()) {
violations.put(obj.getKey(), violation);
}
}
if (!violations.isEmpty()) {
throw new BeanValidationException(violations);
}
}
private Set<ConstraintViolation<Object>> validateObject(Object obj) {
if (obj instanceof Optional) {
Optional<?> opt = (Optional<?>) obj;
if (opt.isPresent()) {
obj = opt.get();
}
}
Set<ConstraintViolation<Object>> violation = null;
if (obj instanceof Synchronizable) {
Lock lock = ((Synchronizable) obj).getLock();
lock.lock();
try {
violation = VALIDATOR.validate(obj);
} finally {
lock.unlock();
}
} else {
violation = VALIDATOR.validate(obj);
}
return violation;
}
private Set<ConstraintViolation<Object>> createViolationForNullRoot() {
ConstraintViolation<Object> viol = new NullRootViolation();
Set<ConstraintViolation<Object>> violSet = ImmutableSet.of(viol);
return violSet;
}
public final class NullRootViolation implements ConstraintViolation<Object> {
private String message = "NULL_ROOT_OBJECT";
@Override
public ConstraintDescriptor<?> getConstraintDescriptor() {
return null;
}
@Override
public <U> U unwrap(Class<U> type) {
return null;
}
@Override
public Object getInvalidValue() {
return null;
}
@Override
public Object getLeafBean() {
return null;
}
@Override
public Object[] getExecutableParameters() {
return new Object[0];
}
@Override
public Object getExecutableReturnValue() {
return null;
}
@Override
public String getMessage() {
return message;
}
@Override
public String getMessageTemplate() {
return message;
}
@Override
public Path getPropertyPath() {
return null;
}
@Override
public Object getRootBean() {
return null;
}
@Override
public Class<Object> getRootBeanClass() {
return null;
}
@Override
public String toString() {
return message;
}
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("objMap", objMap).toString();
}
}