WebServiceOperationLogicImpl.java

package org.andromda.cartridges.webservice.metafacades;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.andromda.cartridges.webservice.WebServiceGlobals;
import org.andromda.cartridges.webservice.WebServiceUtils;
import org.andromda.core.metafacade.MetafacadeBase;
import org.andromda.core.metafacade.ModelValidationMessage;
import org.andromda.metafacades.uml.MetafacadeUtils;
import org.andromda.metafacades.uml.ModelElementFacade;
import org.andromda.metafacades.uml.ParameterFacade;
import org.andromda.metafacades.uml.Service;
import org.andromda.metafacades.uml.UMLProfile;
import org.andromda.translation.ocl.validation.OCLExpressions;
import org.andromda.translation.ocl.validation.OCLIntrospector;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * MetafacadeLogic implementation for org.andromda.cartridges.webservice.metafacades.WebServiceOperation.
 *
 * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation
 * @author Bob Fields
 */
public class WebServiceOperationLogicImpl
    extends WebServiceOperationLogic
{
    private static final long serialVersionUID = 34L;
    /**
     * @param metaObject
     * @param context
     */
    public WebServiceOperationLogicImpl(
        Object metaObject,
        String context)
    {
        super(metaObject, context);
    }

    /**
     * The logger instance.
     */
    private static final Logger logger = Logger.getLogger(WebServiceOperationLogicImpl.class);

    /**
     * @return getOwner().hasStereotype(UMLProfile.STEREOTYPE_WEBSERVICE) or hasStereotype(UMLProfile.STEREOTYPE_WEBSERVICE_OPERATION)
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#isExposed()
     */
    protected boolean handleIsExposed()
    {
        // Private methods are for doc and future use purposes, but are allowed.
        boolean visibility = this.getVisibility().equals("public") || this.getVisibility().equals("protected");
        return visibility && (this.getOwner().hasStereotype(UMLProfile.STEREOTYPE_WEBSERVICE) ||
        this.hasStereotype(UMLProfile.STEREOTYPE_WEBSERVICE_OPERATION));
    }

    /**
     * The prefix given to the test implementation operation names.
     */
    private static final String TEST_IMPLEMENTATION_OPERATION_NAME_PREFIX =
        "testImplementationOperationNamePrefix";

    /**
     * Gets the test implementation operation name prefix.
     */
    private String getTestImplementationOperationNamePrefix()
    {
        return String.valueOf(
            this.getConfiguredProperty(TEST_IMPLEMENTATION_OPERATION_NAME_PREFIX));
    }

    /**
     * @return getTestImplementationOperationNamePrefix() + StringUtils.capitalize(this.getTestName())
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getTestImplementationName()
     */
    protected String handleGetTestImplementationName()
    {
        return this.getTestImplementationOperationNamePrefix() +
        StringUtils.capitalize(this.getTestName());
    }

    /**
     * @return "this." + this.getTestImplementationSignature()
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getTestImplementationCall()
     */
    protected String handleGetTestImplementationCall()
    {
        return "this." + this.getTestImplementationSignature();
    }

    /**
     * @return this.getTestImplementationOperationNamePrefix() + StringUtils.capitalize(this.getTestSignature())
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getTestImplementationSignature()
     */
    protected String handleGetTestImplementationSignature()
    {
        return this.getTestImplementationOperationNamePrefix() +
        StringUtils.capitalize(this.getTestSignature());
    }

    /**
     * @return Operation Signature, taking WSSecurity and WSCustomHeader annotations into account
     * @param withArgumentNames boolean Use argument names.
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getSignature()
     */
    protected String handleGetSignature(final boolean withArgumentNames)
    {
        Collection<ParameterFacade> arguments = this.getArguments();
        String signature = MetafacadeUtils.getSignature(
                this.getName(),
                arguments,
                withArgumentNames,
                null);
        Service service = this.getService();
        if (this.hasStereotype("WSCustomHeader") || this.getService().hasStereotype("WSCustomHeader"))
        {
            String serviceElementName = (String) service.findTaggedValue("andromda_wsdl_header_element");
            String serviceNamespace = (String) service.findTaggedValue("andromda_header_namespace");
            String serviceClassName = WebServiceUtils.getPackageName(serviceNamespace) + '.' + serviceElementName;
            String serviceParameterName = StringUtils.uncapitalize(serviceElementName);
            signature = signature.substring(0, signature.length()-1);
            if (!arguments.isEmpty())
            {
                signature += ", ";
            }
            signature += serviceClassName;
            if (withArgumentNames)
            {
                signature += " " + serviceParameterName;
            }
            signature += ')';
        }
        if (this.hasStereotype("WSSecurity") || this.getService().hasStereotype("WSSecurity"))
        {
            String securityElementName = (String) service.findTaggedValue("andromda_wsdl_security_element");
            String securityNamespace = (String) service.findTaggedValue("andromda_security_namespace");
            String securityClassName = WebServiceUtils.getPackageName(securityNamespace) + '.' + securityElementName;
            String securityParameterName = StringUtils.uncapitalize(securityElementName);
            signature = signature.substring(0, signature.length()-1);
            if (!arguments.isEmpty() || this.hasStereotype("WSCustomHeader") || this.getService().hasStereotype("WSCustomHeader"))
            {
                signature += ", ";
            }
            signature += securityClassName;
            if (withArgumentNames)
            {
                signature += " " + securityParameterName;
            }
            signature += ')';
        }
        return signature;
    }

    /**
     * The prefix given to the junit test operations.
     */
    private static final String TEST_NAME_PREFIX = "test";

    /**
     * @return TEST_NAME_PREFIX + StringUtils.capitalize(this.getName())
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getTestName()
     */
    protected String handleGetTestName()
    {
        return TEST_NAME_PREFIX + StringUtils.capitalize(this.getName());
    }

    /**
     * @return "this." + this.getSignature()
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getTestCall()
     */
    protected String handleGetTestCall()
    {
        return "this." + this.getSignature();
    }

    /**
     * @return this.getTestName() + "()"
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperation#getTestSignature()
     */
    protected String handleGetTestSignature()
    {
        return this.getTestName() + "()";
    }

    /**
     * The property defining the default style to give the web services.
     */
    private static final String PROPERTY_DEFAULT_PARAMETER_STYLE = "defaultParameterStyle";
    private static final String DEFAULT = "default";
    private static final String EMPTY_STRING = "";
    private static final String BOOLEAN_FALSE = "false";

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#handleGetParameterStyle()
     */
    @Override
    protected String handleGetParameterStyle()
    {
        String style = (String)this.findTaggedValue(WebServiceGlobals.WEB_SERVICE_PARAMETER_STYLE);
        if (StringUtils.isEmpty(style) || style.equals(DEFAULT))
        {
            style = String.valueOf(this.getConfiguredProperty(PROPERTY_DEFAULT_PARAMETER_STYLE));
        }
        if (StringUtils.isEmpty(style) || style.equals(DEFAULT))
        {
            style = "wrapped";
        }
        return style;
    }
    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestCacheType()
     */
    @Override
    protected String handleGetRestCacheType()
    {
        String cacheType = (String)this.findTaggedValue(WebServiceGlobals.CACHE_TYPE);
        if (!this.isRest() || StringUtils.isBlank(cacheType) || cacheType.equals(DEFAULT))
        {
            cacheType = EMPTY_STRING;
        }
        return cacheType;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestConsumes()
     */
    @Override
    protected String handleGetRestConsumes()
    {
        String consumes = (String)this.findTaggedValue(WebServiceGlobals.REST_CONSUMES);
        if (!this.isRest() || StringUtils.isBlank(consumes) || consumes.equals(DEFAULT))
        {
            consumes = EMPTY_STRING;
        }
        else
        {
            consumes = translateMediaType(consumes);
        }
        return consumes;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestPartType()
     */
    @Override
    protected String handleGetRestPartType()
    {
        String partType = (String)this.findTaggedValue(WebServiceGlobals.REST_PART_TYPE);
        if (!this.isRest() || StringUtils.isBlank(partType) || partType.equals(DEFAULT))
        {
            partType = EMPTY_STRING;
        }
        return partType;
    }

    private static final String SLASH = "/";
    private static final String QUOTE = "\"";
    private static final String LBRACKET = "{";
    private static final String RBRACKET = "}";
    /**
     * Create default REST URL of /methodname/parameter/{parameter}/
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestPath()
     */
    @Override
    protected String handleGetRestPath()
    {
        String path = (String)this.findTaggedValue(WebServiceGlobals.REST_PATH);
        StringBuilder pathBuffer = new StringBuilder();
        if (!this.isRest() || StringUtils.isBlank(path) || path.equals(DEFAULT))
        {
            pathBuffer.append(QUOTE).append(SLASH).append(this.getName().toLowerCase()).append(SLASH);
            //path = SLASH + this.getName().toLowerCase() + SLASH;
            Iterator<ParameterFacade> parameters = this.getArguments().iterator();
            while (parameters.hasNext())
            {
                ParameterFacade param = parameters.next();
                //if (WebServiceUtils.isSimpleType(param))
                //{
                    String paramName = param.getName();
                    pathBuffer.append(paramName).append(SLASH).append(LBRACKET).append(paramName).append(RBRACKET).append(SLASH);
                //}
            }
            pathBuffer.append(QUOTE);
        }
        else
        {
            if (StringUtils.isBlank(path))
            {
                path = EMPTY_STRING;
            }
            pathBuffer.append(path);
            if (!path.startsWith(QUOTE))
            {
                pathBuffer.insert(0, QUOTE);
            }
            if (!path.endsWith(QUOTE) || path.length()<2)
            {
                pathBuffer.append(QUOTE);
            }
        }
        return pathBuffer.toString();
    }

    /**
     * Create test REST URL of /methodname/parameter/{parameter}/
     * Substitutes test values for parameters
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestPath()
     */
    @Override
    protected String handleGetRestTestPath()
    {
        String path = (String)this.findTaggedValue(WebServiceGlobals.REST_PATH);
        StringBuilder pathBuffer = new StringBuilder();
        WebServiceLogic service = (WebServiceLogic)this.getService();
        String servicePath = service.getRestPath();
        WebServiceUtils wsutils = new WebServiceUtils();
        if (!this.isRest() || StringUtils.isBlank(path) || path.equals(DEFAULT))
        {
            pathBuffer.append(SLASH).append(this.getName().toLowerCase()).append(SLASH);
            Iterator<ParameterFacade> parameters = this.getArguments().iterator();
            while (parameters.hasNext())
            {
                ParameterFacade param = parameters.next();
                //System.out.println("handleGetRestTestPath param=" + param.getName() + " servicePath=" + servicePath + " value=" + wsutils.createConstructor(param));
                if (WebServiceUtils.isSimpleType(param))
                {
                    String paramValue = wsutils.createConstructor(param);
                    // Only use the value if constructor returns new Class()
                    if (paramValue.indexOf('(') > 0)
                    {
                        paramValue = paramValue.substring(paramValue.indexOf('(')+1, paramValue.indexOf(')'));
                    }
                    pathBuffer.append(param.getName()).append(SLASH).append(paramValue).append(SLASH);
                }
            }
            path = pathBuffer.toString();
        }
        else
        {
            if (StringUtils.isBlank(path))
            {
                path = EMPTY_STRING;
            }
            // StringBuffer doesn't have replace(String, String) API
            path = pathBuffer.append(path).toString();
            Iterator<ParameterFacade> parameters = this.getArguments().iterator();
            while (parameters.hasNext())
            {
                ParameterFacade param = parameters.next();
                if (WebServiceUtils.isSimpleType(param))
                {
                    String paramValue = wsutils.createConstructor(param).replace("\"", "");
                    if (paramValue.indexOf('(') > 0)
                    {
                        paramValue = paramValue.substring(paramValue.indexOf('(')+1, paramValue.indexOf(')'));
                    }
                    path = StringUtils.replace(path, LBRACKET + param.getName() + RBRACKET, paramValue);
                }
                //System.out.println("handleGetRestTestPath param=" + param.getName() + " servicePath=" + servicePath + " value=" + wsutils.createConstructor(param) + " path=" + path);
            }
        }
        path = servicePath + path;
        path = path.replaceAll("\"", "");
        path = path.replaceAll("//", "/");
        return path;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestProduces()
     */
    @Override
    protected String handleGetRestProduces()
    {
        String produces = (String)this.findTaggedValue(WebServiceGlobals.REST_PRODUCES);
        // default types: text for simple types, XML for complex types
        if (!this.isRest() || produces == DEFAULT)
        {
            // See if the service class has REST_produces attribute set...
            produces = (String)this.getOwner().findTaggedValue(WebServiceGlobals.REST_PRODUCES);
            if (produces == DEFAULT)
            {
                // Default produces type for simple or complex return types
                if (WebServiceUtils.isSimpleType(this.getReturnType()))
                {
                    produces = "text/plain";
                }
                else
                {
                    produces = "application/xml";
                }
            }
        }
        else
        {
            produces = translateMediaType(produces);
        }
        return produces;
    }

    /**
     * Returns map of ProviderMediaType enumeration values to Provider/Consumer text
     */
    private static Map<String, String> getMediaTranslation()
    {
        final Map<String, String> mediaMap = new HashMap<String, String>();
        mediaMap.put("default", QUOTE + "application/xml" + QUOTE);
        mediaMap.put("ApplicationAtom", QUOTE + "application/atom+xml" + QUOTE);
        mediaMap.put("ApplicationAtomEntry", QUOTE + "application/atom+xml;type=entry" + QUOTE);
        mediaMap.put("ApplicationAtomXML", QUOTE + "application/atom+xml" + QUOTE);
        mediaMap.put("ApplicationFastinfoset", QUOTE + "application/fastinfoset" + QUOTE);
        mediaMap.put("ApplicationFormURLEncoded", QUOTE + "application/x-www-form-urlencoded" + QUOTE);
        mediaMap.put("ApplicationJSON", QUOTE + "application/json" + QUOTE);
        mediaMap.put("ApplicationOctetStream", QUOTE + "application/octet-stream" + QUOTE);
        mediaMap.put("ApplicationSvgXML", QUOTE + "application/svg+xml" + QUOTE);
        mediaMap.put("ApplicationXhtmlXML", QUOTE + "application/xhtml+xml" + QUOTE);
        mediaMap.put("ApplicationXML", QUOTE + "application/xml" + QUOTE);
        mediaMap.put("ApplicationXMLAll", QUOTE + "application/*+xml" + QUOTE);
        mediaMap.put("ApplicationYaml", QUOTE + "application/yaml" + QUOTE);
        mediaMap.put("MultipartAlternative", QUOTE + "multipart/alternative" + QUOTE);
        mediaMap.put("MultipartFixed", QUOTE + "multipart/fixed" + QUOTE);
        mediaMap.put("MultipartFormData", QUOTE + "multipart/form-data" + QUOTE);
        mediaMap.put("MultipartMixed", QUOTE + "multipart/mixed" + QUOTE);
        mediaMap.put("MultipartRelated", QUOTE + "multipart/related" + QUOTE);
        mediaMap.put("TextPlain", QUOTE + "text/plain" + QUOTE);
        mediaMap.put("TextXML", QUOTE + "text/xml" + QUOTE);
        mediaMap.put("TextXYaml", QUOTE + "text/xyaml" + QUOTE);
        mediaMap.put("TextYaml", QUOTE + "text/xml" + QUOTE);
        mediaMap.put("Wildcard", QUOTE + "*/*" + QUOTE);
        return mediaMap;
    }

    /**
     * Translates Media Enumeration Type into string for produces/consumes annotation
     * @param input ProviderMediaType Enumeration value to be translated for Annotation
     * @return getMediaTranslation().get(input)
     */
    public static String translateMediaType(String input)
    {
        String output = null;
        if (StringUtils.isBlank(input) || input.equals(DEFAULT) || !getMediaTranslation().containsKey(input))
        {
            output = getMediaTranslation().get(DEFAULT);
        }
        else
        {
            output = getMediaTranslation().get(input);
        }
        return output;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestProvider()
     */
    @Override
    protected String handleGetRestProvider()
    {
        String provider = (String)this.findTaggedValue(WebServiceGlobals.REST_PROVIDER);
        if (!this.isRest() || StringUtils.isBlank(provider) || provider.equals(DEFAULT))
        {
            provider = EMPTY_STRING;
        }
        return provider;
    }

    private static final String GET = "@javax.ws.rs.GET";
    private static final String AT = "@javax.ws.rs.";
    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestRequestType()
     */
    @Override
    protected String handleGetRestRequestType()
    {
        String requestType = (String)this.findTaggedValue(WebServiceGlobals.REST_REQUEST_TYPE);
        if (!this.isRest() || StringUtils.isBlank(requestType) || requestType.equals(DEFAULT))
        {
            requestType = GET;
        }
        else if (!requestType.startsWith(AT))
        {
            requestType = AT + requestType;
        }
        return requestType;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRestRequestType()
     */
    @Override
    protected int handleGetRestSuspend()
    {
        String suspend = (String)this.findTaggedValue(WebServiceGlobals.REST_SUSPEND);
        if (!this.isRest() || StringUtils.isBlank(suspend) || suspend.equals(DEFAULT) || !StringUtils.isNumeric(suspend))
        {
            return 0;
        }
        return Integer.parseInt(suspend);
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRolesAllowed()
     */
    @Override
    protected String handleGetRolesAllowed()
    {
        String rolesAllowed = (String)this.findTaggedValue(WebServiceGlobals.REST_ROLES_ALLOWED);
        if (!this.isRest() || StringUtils.isBlank(rolesAllowed) || rolesAllowed.equals(DEFAULT))
        {
            rolesAllowed = EMPTY_STRING;
        }
        return rolesAllowed;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#handleIsRest()
     */
    @Override
    protected boolean handleIsRest()
    {
        String rest = (String)this.findTaggedValue(WebServiceGlobals.REST);
        if (StringUtils.isBlank(rest) || rest.equals(DEFAULT))
        {
            rest = (String)this.getOwner().findTaggedValue(WebServiceGlobals.REST);
            if (StringUtils.isBlank(rest) || rest.equals(DEFAULT))
            {
                rest = BOOLEAN_FALSE;
            }
        }
        return Boolean.valueOf(rest);
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#getRolesAllowed()
     */
    @Override
    protected boolean handleIsRestEncoded()
    {
        String restEncoded = (String)this.findTaggedValue(WebServiceGlobals.REST_ENCODED);
        if (!this.isRest() || StringUtils.isBlank(restEncoded) || restEncoded.equals(DEFAULT))
        {
            restEncoded = BOOLEAN_FALSE;
        }
        return Boolean.valueOf(restEncoded);
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceLogic#isRestAtom()
     */
    @Override
    protected boolean handleIsRestAtom()
    {
        WebServiceLogic service = (WebServiceLogic)this.getService();
        boolean rest = this.isRest();
        boolean restAtom = false;
        if (rest)
        {
            restAtom = this.getRestProduces().contains("atom");
            if (!restAtom)
            {
                restAtom = service.getRestProduces().indexOf("atom") > -1;
            }
        }
        return restAtom;
    }

    /**
     * Return the value from WebServiceOperation andromda_webservice_operationName, or just the operation.name
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#handleGetOperationName()
     */
    @Override
    protected String handleGetOperationName()
    {
        String serviceName = (String)this.findTaggedValue(WebServiceGlobals.WEB_SERVICE_NAME);
        if (StringUtils.isBlank(serviceName))
        {
            serviceName = this.getName();
        }
        return serviceName;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#handleIsWebFaultOnAllExceptions()
     */
    @Override
    protected boolean handleIsWebFaultOnAllExceptions()
    {
        boolean result = true;
        String webserviceStack = String.valueOf(this.getConfiguredProperty("webserviceStack"));
        if (webserviceStack.equals("cxf") || webserviceStack.equals("jaxws"))
        {
            Collection<ModelElementFacade> exceptions = this.getExceptions();
            for (ModelElementFacade exception : exceptions)
            {
                // Log the missing exception class, since validation message only shows the service operation name
                if (!exception.hasStereotype(UMLProfile.STEREOTYPE_WEB_FAULT))
                {
                    result = false;
                    logger.warn(exception.getFullyQualifiedName() + " WebFault stereotype missing from operation exception thrown by " + this.getFullyQualifiedName());
                }
            }
        }
        return result;
    }

    /**
     * @see org.andromda.cartridges.webservice.metafacades.WebServiceOperationLogic#handleGetWebServicePackage()
     */
    @Override
    protected ModelElementFacade handleGetWebServicePackage()
    {
        return this.getOwner().getPackage();
    }

    /**
     * <p><b>Constraint:</b> org::andromda::cartridges::webservice::metafacades::WebServiceOperation::operation must start with a lowercase letter</p>
     * <p><b>Error:</b> Operation name must start with a lowercase letter.</p>
     * @param validationMessages Collection<ModelValidationMessage>
     * @see MetafacadeBase#validateInvariants(Collection validationMessages)
     */
    @Override
    public void validateInvariants(Collection<ModelValidationMessage> validationMessages)
    {
        super.validateInvariants(validationMessages);
        try
        {
            final Object contextElement = this.THIS();
            final String name = (String)OCLIntrospector.invoke(contextElement,"name");
            boolean constraintValid = OCLExpressions.equal(
                name.substring(0,1).toLowerCase(),
                name.substring(0,1));
            if (!constraintValid)
            {
                validationMessages.add(
                    new ModelValidationMessage(
                        (MetafacadeBase)contextElement ,
                        "org::andromda::cartridges::webservice::metafacades::WebServiceOperation::operation must start with a lowercase letter",
                        "Operation name must start with a lowercase letter."));
            }
        }
        catch (Throwable th)
        {
            Throwable cause = th.getCause();
            int depth = 0; // Some throwables have infinite recursion
            while (cause != null && depth < 7)
            {
                th = cause;
                depth++;
            }
            logger.error("Error validating constraint 'org::andromda::cartridges::webservice::WebServicePackage::operation must start with a lowercase letter' ON "
                + this.THIS().toString() + ": " + th.getMessage(), th);
        }
    }
}