/*
* Copyright (c) 2010 Red Hat, Inc.
*
* 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 org.ovirt.engine.api.common.util;
import static org.ovirt.engine.api.utils.ReflectionHelper.capitalize;
import static org.ovirt.engine.api.utils.ReflectionHelper.get;
import static org.ovirt.engine.api.utils.ReflectionHelper.invoke;
import static org.ovirt.engine.api.utils.ReflectionHelper.isSet;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.ovirt.engine.api.model.Fault;
/**
* Used to validate that the required fields are set on a user-provided
* model instance
*/
public class CompletenessAssertor {
// REVISIT: i18n
private static final String INCOMPLETE_PARAMS_REASON = "Incomplete parameters";
private static final String INCOMPLETE_PARAMS_DETAIL = "{0} {1} required for {2}";
private static final String ALTERNATIVE = "\\|";
private static final String DELIMITER = "\\.";
private static final Response.Status INCOMPLETE_PARAMS_STATUS = Response.Status.BAD_REQUEST;
/**
* Validate presence of required parameters.
* Note the model type is java.lang.Object as opposed to a generic
* <T extends BaseResource> in order to accommodate parameters types
* such as Action.
*
* @param model the incoming representation
* @param required the required field names
* @throws WebApplicationException wrapping an appropriate response
* iff a required parameter is missing
*/
public static void validateParameters(Object model, String... required) {
validateParameters(INCOMPLETE_PARAMS_REASON, INCOMPLETE_PARAMS_DETAIL, model, 2, required);
}
/**
* Validate presence of required parameters.
* Note the model type is java.lang.Object as opposed to a generic
* <T extends BaseResource> in order to accommodate parameters types
* such as Action.
*
* @param reason the fault reason
* @param detail the fault detail
* @param model the incoming representation
* @param required the required field names
* @throws WebApplicationException wrapping an appropriate response
* iff a required parameter is missing
*/
public static void validateParameters(String reason, String detail, Object model, String... required) {
validateParameters(reason, detail, model, 2, required);
}
/**
* Validate presence of required parameters.
* Note the model type is java.lang.Object as opposed to a generic
* <T extends BaseResource> in order to accommodate parameters types
* such as Action.
*
* @param model the incoming representation
* @param required the required field names
* @param frameOffset the stack frame offset of the public resource method
* @throws WebApplicationException wrapping an appropriate response
* iff a required parameter is missing
*/
public static void validateParameters(Object model, int frameOffset, String... required) {
Response error = assertRequired(INCOMPLETE_PARAMS_REASON, INCOMPLETE_PARAMS_DETAIL, model, frameOffset, required);
if (error != null) {
throw new WebApplicationException(error);
}
}
/**
* Validate presence of required parameters.
* Note the model type is java.lang.Object as opposed to a generic
* <T extends BaseResource> in order to accommodate parameters types
* such as Action.
*
* @param reason the fault reason
* @param detail the fault detail
* @param model the incoming representation
* @param required the required field names
* @param frameOffset the stack frame offset of the public resource method
* @throws WebApplicationException wrapping an appropriate response
* iff a required parameter is missing
*/
public static void validateParameters(String reason, String detail, Object model, int frameOffset, String... required) {
Response error = assertRequired(reason, detail, model, frameOffset, required);
if (error != null) {
throw new WebApplicationException(error);
}
}
/**
* Validate presence of required parameters.
*
* @param reason the fault reason
* @param detail the fault detail
* @param model the incoming representation
* @param frameOffset the stack frame offset of the public resource method
* @param missingMembers the stack frame offset of the public resource method
* @param required the required field names
* @return error Response if appropriate
*/
private static Response assertRequired(String reason, String detail, Object model, int frameOffset, String... required) {
List<String> missing = doAssertRequired(reason, detail, model, frameOffset, required);
Response response = null;
if (!missing.isEmpty()) {
StackTraceElement[] trace = new Throwable().getStackTrace();
Fault fault = new Fault();
fault.setReason(reason);
fault.setDetail(MessageFormat.format(detail,
model.getClass().getSimpleName(),
missing,
trace[frameOffset + 1].getMethodName()));
response = Response.status(INCOMPLETE_PARAMS_STATUS)
.entity(fault)
.build();
}
return response;
}
/**
* Validate presence of required parameters.
*
* @param reason the fault reason
* @param detail the fault detail
* @param model the incoming representation
* @param frameOffset the stack frame offset of the public resource method
* @param required the required field names
* @return error Response if appropriate
*/
private static List<String> doAssertRequired(String reason, String detail, Object model, int frameOffset, String... required) {
List<String> missing = new ArrayList<>();
for (String r : required) {
if (topLevel(r)) {
if (!assertFields(model, subField(r))) {
missing.add(r);
}
} else if (isList(model, superField(r))) {
for (Object item : asList(model, superField(r))) {
if (!assertFields(item, subField(r))) {
missing.add(r);
}
}
} else if(!isLeaf(r)){
Object superType = get(model, superField(r));
if(superType != null) {
missing.addAll(joinSuperType(doAssertRequired(reason, detail, superType, frameOffset, subField(r)), superType));
} else {
missing.add(r);
}
}
else {
boolean found = false;
for (String superField : superField(r).split(ALTERNATIVE)) {
found = found || (isSet(model, capitalize(superField)) && assertFields(model, superField, subField(r)));
}
if (!found) {
missing.add(r);
}
}
}
return missing;
}
/**
* add SuperType to missing arguments
*
* @param missing the missing arguments
* @param superType the super type to join
* @return collection of missing parameters
*/
private static Collection<? extends String> joinSuperType(List<String> missing, Object superType) {
String superTypeName = superType.getClass().getSimpleName().toLowerCase();
for(int i = 0; i < missing.size(); i++){
missing.set(i, superTypeName + "." + missing.get(i));
}
return missing;
}
private static boolean assertFields(Object model, String fields) {
return assertFields(model, null, fields);
}
private static boolean assertFields(Object model, String superField, String subFields) {
String[] splitFields = subFields.split(ALTERNATIVE);
boolean found = false;
for (String subField : splitFields) {
found = found || isSet(superField != null ? get(model, superField) : model, capitalize(subField));
}
return found;
}
private static boolean topLevel(String required) {
return required.indexOf(".") == -1;
}
/**
* Checks if this type is leaf in arguments
*
* @param required the type to check
* @return boolean
*/
private static boolean isLeaf(String required) {
String[] res = required.split(DELIMITER);
return res == null || res.length <= 2;
}
private static String superField(String required) {
return capitalize(required.substring(0, required.indexOf(".")));
}
private static String subField(String required) {
return required.substring(required.indexOf(".") + 1);
}
@SuppressWarnings("unchecked")
private static boolean isList(Object model, String name) {
if (model == null) {
return false;
}
Object value = get(model, name);
if (value == null) {
return false;
}
Class<? extends Object> clazz = value.getClass();
if (List.class.isAssignableFrom(clazz)) {
return true;
}
Method getter = null;
int count = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().startsWith("get")) {
getter = method;
count++;
}
}
return count == 1 && List.class.isAssignableFrom(getter.getReturnType());
}
@SuppressWarnings("unchecked")
private static List<Object> asList(Object model, String name) {
Object value = get(model, name);
if (value == null) {
return Collections.emptyList();
}
Class<? extends Object> clazz = value.getClass();
if (List.class.isAssignableFrom(clazz)) {
return (List<Object>) value;
}
Method getter = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().startsWith("get") && List.class.isAssignableFrom(method.getReturnType())) {
getter = method;
break;
}
}
if (getter == null) {
return Collections.emptyList();
}
return (List<Object>) invoke(value, getter);
}
}