/*******************************************************************************
* Copyright © 2011, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.mof.eglx.services.validation;
import java.util.HashMap;
import org.eclipse.edt.compiler.core.IEGLConstants;
import org.eclipse.edt.compiler.core.ast.FunctionParameter;
import org.eclipse.edt.compiler.core.ast.FunctionParameter.UseType;
import org.eclipse.edt.compiler.core.ast.NestedFunction;
import org.eclipse.edt.compiler.internal.core.builder.IMarker;
import org.eclipse.edt.mof.egl.Annotation;
import org.eclipse.edt.mof.egl.ArrayType;
import org.eclipse.edt.mof.egl.Container;
import org.eclipse.edt.mof.egl.EnumerationEntry;
import org.eclipse.edt.mof.egl.Function;
import org.eclipse.edt.mof.egl.Handler;
import org.eclipse.edt.mof.egl.Member;
import org.eclipse.edt.mof.egl.Record;
import org.eclipse.edt.mof.egl.Type;
import org.eclipse.edt.mof.egl.utils.IRUtils;
import org.eclipse.edt.mof.egl.utils.TypeUtils;
import org.eclipse.edt.mof.eglx.services.ext.Utils;
import org.eclipse.edt.mof.eglx.services.messages.ResourceKeys;
import org.eclipse.edt.mof.utils.NameUtile;
/**
* @author demurray
*/
public class RestServiceProxyFunctionValidator extends ServiceProxyFunctionValidator {
private static class SubstitutionVar {
int startOffset;
int endOffset;
String varName;
public SubstitutionVar(int startOffset, int endOffset,
String uriTemplate) {
super();
this.endOffset = endOffset;
this.startOffset = startOffset;
this.varName = uriTemplate.substring(startOffset, endOffset);
}
public int getStartOffset() {
return startOffset;
}
public int getEndOffset() {
return endOffset;
}
public String getVarName() {
return varName;
}
}
@Override
protected Annotation getAnnotation(Function function) {
return function.getAnnotation("eglx.rest.Rest");
}
@Override
protected void validate(NestedFunction nestedFunction) {
Function function = (Function) nestedFunction.getName().resolveMember();
Annotation restAnnotation = getAnnotation(function);
if (function.getReturnType() != null) {
// If the function returns a type, it must be a resource
if (!isResourceType(function.getReturnType(), restAnnotation)) {
problemRequestor.acceptProblem(nestedFunction.getReturnType(),
ResourceKeys.XXXREST_MUST_RETURN_RESOURCE,
IMarker.SEVERITY_ERROR,
new String[] { function.getCaseSensitiveName(), getName() },
ResourceKeys.getResourceBundleForKeys());
}
// If the return type is String, the responseFormat must be NONE if
// is is specified
if (function.getReturnType().equals(TypeUtils.Type_STRING)) {
EnumerationEntry respForm = (EnumerationEntry) restAnnotation.getValue(IEGLConstants.PROPERTY_RESPONSEFORMAT);
if (respForm != null) {
problemRequestor.acceptProblem(
nestedFunction.getReturnType(),
ResourceKeys.XXXREST_FORMAT_MUST_BE_NONE,
IMarker.SEVERITY_ERROR,
new String[] { IEGLConstants.PROPERTY_RESPONSEFORMAT },
ResourceKeys.getResourceBundleForKeys());
}
}
}
HashMap<String, FunctionParameter> parmNamesToNodes = new HashMap<String, FunctionParameter>();
// All parameters must be IN
for (Object parm : nestedFunction.getFunctionParameters()) {
parmNamesToNodes.put(((FunctionParameter)parm).getName().getCanonicalName().toLowerCase(),(FunctionParameter)parm);
if (((FunctionParameter)parm).getUseType() != UseType.IN) {
problemRequestor.acceptProblem((FunctionParameter)parm,
ResourceKeys.XXXREST_ALL_PARMS_MUST_BE_IN,
IMarker.SEVERITY_ERROR,
new String[] { ((FunctionParameter)parm).getName().getCanonicalName(),
function.getCaseSensitiveName(), getName() },
ResourceKeys.getResourceBundleForKeys());
}
}
HashMap<String, SubstitutionVar> namesToSubstitutionVars = parseSubtitutionVars(restAnnotation);
// loop through the parms and do resource/non-resource checks
boolean foundResourceParm = false;
for (FunctionParameter parm : parmNamesToNodes.values()) {
if (namesToSubstitutionVars.get(NameUtile.getAsName(parm.getName().getCanonicalName())) == null) {
if (supportsResourceParm(restAnnotation)) {
// Only 1 resource parameter is allowed!
if (foundResourceParm) {
problemRequestor.acceptProblem(parm,
ResourceKeys.XXXREST_ONLY_1_RESOURCE_PARM,
IMarker.SEVERITY_ERROR,
new String[] { function.getCaseSensitiveName(), getName(),
parm.getName().getCanonicalName() },
ResourceKeys.getResourceBundleForKeys());
} else {
foundResourceParm = true;
// resource param must be resource
if (parm.getType().resolveType() != null) {
if (!isResourceType(parm.getType().resolveType(),
restAnnotation)) {
problemRequestor.acceptProblem(
parm,
ResourceKeys.XXXREST_RESOURCE_PARM_MUST_BE_RESOURCE,
IMarker.SEVERITY_ERROR,
new String[] {
parm.getName().getCanonicalName(),
function.getCaseSensitiveName(),
getName() },
ResourceKeys.getResourceBundleForKeys());
} else {
// if the resource parameter is String, the
// request Format has to be NONE if specified
if (parm.getType().resolveType().equals(TypeUtils.Type_STRING)) {
EnumerationEntry reqForm = (EnumerationEntry) restAnnotation.getValue(IEGLConstants.PROPERTY_REQUESTFORMAT);
if (reqForm != null
&& !NameUtile.equals(NameUtile.getAsName("none"), reqForm.getName())) {
problemRequestor.acceptProblem(
Utils.getRequestFormat(nestedFunction),
ResourceKeys.XXXREST_FORMAT_MUST_BE_NONE,
IMarker.SEVERITY_ERROR,
new String[] { IEGLConstants.PROPERTY_REQUESTFORMAT },
ResourceKeys.getResourceBundleForKeys());
}
}
// If the requestFormat is formData, then the
// resource parameter must be a flat record
EnumerationEntry reqForm = (EnumerationEntry) restAnnotation.getValue(IEGLConstants.PROPERTY_REQUESTFORMAT);
if (reqForm != null && NameUtile.equals(NameUtile.getAsName("_form"), reqForm.getName())) {
org.eclipse.edt.mof.egl.Type type = parm.getType().resolveType();
if (!isFlatRecord(type)) {
problemRequestor.acceptProblem(
parm,
ResourceKeys.XXXREST_PARM_TYPE_MUST_BE_FLAT_RECORD,
IMarker.SEVERITY_ERROR,
new String[] {
parm.getName().getCanonicalName(),
function.getCaseSensitiveName() },
ResourceKeys
.getResourceBundleForKeys());
}
}
}
}
}
} else {
problemRequestor.acceptProblem(nestedFunction,
ResourceKeys.XXXREST_NO_RESOURCE_PARM,
IMarker.SEVERITY_ERROR,
new String[] { function.getCaseSensitiveName(), getName(),
parm.getName().getCanonicalName() },
ResourceKeys.getResourceBundleForKeys());
}
} else {
// non resource parm must be compatible with String
if (parm.getType().resolveType() != null) {
Member member = parm.getName().resolveMember();
if (member != null && !IRUtils.isMoveCompatible(TypeUtils.Type_STRING, member.getType(), member)) {
problemRequestor.acceptProblem(
parm,
ResourceKeys.XXXREST_NON_RESOUCE_MUST_BE_STRING_COMPAT,
IMarker.SEVERITY_ERROR,
new String[] {
parm.getName().getCanonicalName(),
function.getCaseSensitiveName(), getName() },
ResourceKeys.getResourceBundleForKeys());
}
}
}
}
// loop through the substitution variables
for (String key : namesToSubstitutionVars.keySet()) {
SubstitutionVar var = namesToSubstitutionVars.get(key);
int absStart = Utils.getUriTemplateNode(nestedFunction).getOffset() + 1;
FunctionParameter parm = (FunctionParameter) parmNamesToNodes.get(key);
if (parm == null) {
// substitution variable must match a parameter name
problemRequestor.acceptProblem(absStart + var.getStartOffset(),
absStart + var.getEndOffset(), IMarker.SEVERITY_ERROR,
ResourceKeys.XXXREST_UMATCHED_SUBS_VAR, new String[] {
var.getVarName(), function.getCaseSensitiveName() },
ResourceKeys.getResourceBundleForKeys());
} else {
}
}
}
private boolean isResourceType(org.eclipse.edt.mof.egl.Type type, Annotation restAnnotation) {
if (type == null) {
return false;
}
// If responseFormat is JSON, allow single dimension array of strings or
// flexible records
EnumerationEntry responseFormat = (EnumerationEntry) restAnnotation.getValue(IEGLConstants.PROPERTY_RESPONSEFORMAT);
if (responseFormat != null && NameUtile.equals(NameUtile.getAsName("json"), responseFormat.getName()) &&
type instanceof ArrayType) {
type = ((ArrayType) type).getElementType();
}
if (type.equals(TypeUtils.Type_STRING)) {
return true;
}
return type instanceof Record;
}
private boolean isFlatRecord(Type type) {
if (type == null) {
return false;
}
if (isSupportedContainer(type)) {
for (Member member : ((Container) type).getMembers()) {
if (member.getType() instanceof Record) {
return false;
}
}
return true;
}
if (type instanceof Handler) {
for (Member member : ((Handler)type).getMembers()) {
if (isSupportedContainer(member.getType())) {
return false;
}
}
return true;
}
return false;
}
private boolean isSupportedContainer(Type type){
return type instanceof Record ||
type instanceof Handler;
}
private HashMap<String, SubstitutionVar> parseSubtitutionVars( Annotation restAnnotation) {
HashMap<String, SubstitutionVar> namesToSubstitutionVars = new HashMap<String, SubstitutionVar>();
String uriTemp = (String) restAnnotation.getValue(IEGLConstants.PROPERTY_URITEMPLATE);
char[] chars = uriTemp != null ? uriTemp.toCharArray() : new char[0];
int lOffset = 0;
boolean lookingForL = true;
for (int i = 0; i < chars.length; i++) {
if (lookingForL) {
if (chars[i] == '{') {
lOffset = i;
lookingForL = false;
}
} else {
if (chars[i] == '}') {
SubstitutionVar var = new SubstitutionVar(lOffset + 1, i, uriTemp);
namesToSubstitutionVars.put(NameUtile.getAsName(var.getVarName()), var);
lookingForL = true;
}
}
}
return namesToSubstitutionVars;
}
private boolean supportsResourceParm(Annotation restAnnotation) {
EnumerationEntry method = (EnumerationEntry) restAnnotation.getValue("method");
return !(method != null && "_get".equalsIgnoreCase(method.getName()));
}
@Override
protected String getName() {
return "REST";
}
}