Package com.cloud.bridge.service

Source Code of com.cloud.bridge.service.EC2RestServlet

// 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 com.cloud.bridge.service;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import javax.inject.Inject;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.databinding.ADBBean;
import org.apache.axis2.databinding.ADBException;
import org.apache.axis2.databinding.utils.writer.MTOMAwareXMLSerializer;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

import com.amazon.ec2.AllocateAddressResponse;
import com.amazon.ec2.AssociateAddressResponse;
import com.amazon.ec2.AttachVolumeResponse;
import com.amazon.ec2.AuthorizeSecurityGroupIngressResponse;
import com.amazon.ec2.CreateImageResponse;
import com.amazon.ec2.CreateKeyPairResponse;
import com.amazon.ec2.CreateSecurityGroupResponse;
import com.amazon.ec2.CreateSnapshotResponse;
import com.amazon.ec2.CreateTagsResponse;
import com.amazon.ec2.CreateVolumeResponse;
import com.amazon.ec2.DeleteKeyPairResponse;
import com.amazon.ec2.DeleteSecurityGroupResponse;
import com.amazon.ec2.DeleteSnapshotResponse;
import com.amazon.ec2.DeleteTagsResponse;
import com.amazon.ec2.DeleteVolumeResponse;
import com.amazon.ec2.DeregisterImageResponse;
import com.amazon.ec2.DescribeAvailabilityZonesResponse;
import com.amazon.ec2.DescribeImageAttributeResponse;
import com.amazon.ec2.DescribeImagesResponse;
import com.amazon.ec2.DescribeInstanceAttributeResponse;
import com.amazon.ec2.DescribeInstancesResponse;
import com.amazon.ec2.DescribeKeyPairsResponse;
import com.amazon.ec2.DescribeSecurityGroupsResponse;
import com.amazon.ec2.DescribeSnapshotsResponse;
import com.amazon.ec2.DescribeTagsResponse;
import com.amazon.ec2.DescribeVolumesResponse;
import com.amazon.ec2.DetachVolumeResponse;
import com.amazon.ec2.DisassociateAddressResponse;
import com.amazon.ec2.GetPasswordDataResponse;
import com.amazon.ec2.ImportKeyPairResponse;
import com.amazon.ec2.ModifyImageAttributeResponse;
import com.amazon.ec2.ModifyInstanceAttributeResponse;
import com.amazon.ec2.RebootInstancesResponse;
import com.amazon.ec2.RegisterImageResponse;
import com.amazon.ec2.ReleaseAddressResponse;
import com.amazon.ec2.ResetImageAttributeResponse;
import com.amazon.ec2.RevokeSecurityGroupIngressResponse;
import com.amazon.ec2.RunInstancesResponse;
import com.amazon.ec2.StartInstancesResponse;
import com.amazon.ec2.StopInstancesResponse;
import com.amazon.ec2.TerminateInstancesResponse;
import com.cloud.bridge.model.UserCredentialsVO;
import com.cloud.bridge.persist.dao.CloudStackUserDaoImpl;
import com.cloud.bridge.persist.dao.OfferingDaoImpl;
import com.cloud.bridge.persist.dao.UserCredentialsDaoImpl;
import com.cloud.bridge.service.controller.s3.ServiceProvider;
import com.cloud.bridge.service.core.ec2.EC2AddressFilterSet;
import com.cloud.bridge.service.core.ec2.EC2AssociateAddress;
import com.cloud.bridge.service.core.ec2.EC2AuthorizeRevokeSecurityGroup;
import com.cloud.bridge.service.core.ec2.EC2AvailabilityZonesFilterSet;
import com.cloud.bridge.service.core.ec2.EC2CreateImage;
import com.cloud.bridge.service.core.ec2.EC2CreateKeyPair;
import com.cloud.bridge.service.core.ec2.EC2CreateVolume;
import com.cloud.bridge.service.core.ec2.EC2DeleteKeyPair;
import com.cloud.bridge.service.core.ec2.EC2DescribeAddresses;
import com.cloud.bridge.service.core.ec2.EC2DescribeAvailabilityZones;
import com.cloud.bridge.service.core.ec2.EC2DescribeImageAttribute;
import com.cloud.bridge.service.core.ec2.EC2DescribeImages;
import com.cloud.bridge.service.core.ec2.EC2DescribeInstances;
import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairs;
import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroups;
import com.cloud.bridge.service.core.ec2.EC2DescribeSnapshots;
import com.cloud.bridge.service.core.ec2.EC2DescribeTags;
import com.cloud.bridge.service.core.ec2.EC2DescribeVolumes;
import com.cloud.bridge.service.core.ec2.EC2DisassociateAddress;
import com.cloud.bridge.service.core.ec2.EC2Engine;
import com.cloud.bridge.service.core.ec2.EC2Filter;
import com.cloud.bridge.service.core.ec2.EC2GroupFilterSet;
import com.cloud.bridge.service.core.ec2.EC2Image;
import com.cloud.bridge.service.core.ec2.EC2ImageAttributes.ImageAttribute;
import com.cloud.bridge.service.core.ec2.EC2ImageFilterSet;
import com.cloud.bridge.service.core.ec2.EC2ImageLaunchPermission;
import com.cloud.bridge.service.core.ec2.EC2ImportKeyPair;
import com.cloud.bridge.service.core.ec2.EC2InstanceFilterSet;
import com.cloud.bridge.service.core.ec2.EC2IpPermission;
import com.cloud.bridge.service.core.ec2.EC2KeyPairFilterSet;
import com.cloud.bridge.service.core.ec2.EC2ModifyImageAttribute;
import com.cloud.bridge.service.core.ec2.EC2ModifyInstanceAttribute;
import com.cloud.bridge.service.core.ec2.EC2RebootInstances;
import com.cloud.bridge.service.core.ec2.EC2RegisterImage;
import com.cloud.bridge.service.core.ec2.EC2ReleaseAddress;
import com.cloud.bridge.service.core.ec2.EC2RunInstances;
import com.cloud.bridge.service.core.ec2.EC2SecurityGroup;
import com.cloud.bridge.service.core.ec2.EC2SnapshotFilterSet;
import com.cloud.bridge.service.core.ec2.EC2StartInstances;
import com.cloud.bridge.service.core.ec2.EC2StopInstances;
import com.cloud.bridge.service.core.ec2.EC2Tags;
import com.cloud.bridge.service.core.ec2.EC2TagsFilterSet;
import com.cloud.bridge.service.core.ec2.EC2Volume;
import com.cloud.bridge.service.core.ec2.EC2VolumeFilterSet;
import com.cloud.bridge.service.exception.EC2ServiceException;
import com.cloud.bridge.service.exception.EC2ServiceException.ClientError;
import com.cloud.bridge.service.exception.NoSuchObjectException;
import com.cloud.bridge.service.exception.PermissionDeniedException;
import com.cloud.bridge.util.AuthenticationUtils;
import com.cloud.bridge.util.ConfigurationHelper;
import com.cloud.bridge.util.EC2RestAuth;
import com.cloud.stack.models.CloudStackAccount;
import com.cloud.utils.db.TransactionLegacy;

@Component("EC2RestServlet")
public class EC2RestServlet extends HttpServlet {

    private static final long serialVersionUID = -6168996266762804888L;
    @Inject
    UserCredentialsDaoImpl ucDao;
    @Inject
    OfferingDaoImpl ofDao;
    @Inject
    CloudStackUserDaoImpl userDao;

    public static final Logger logger = Logger.getLogger(EC2RestServlet.class);

    private final OMFactory factory = OMAbstractFactory.getOMFactory();
    private final XMLOutputFactory xmlOutFactory = XMLOutputFactory.newInstance();

    private String pathToKeystore = null;
    private String keystorePassword = null;
    private String wsdlVersion = null;
    private String version = null;

    boolean debug = true;

    public EC2RestServlet() {
    }

    /**
     * We build the path to where the keystore holding the WS-Security X509 certificates
     * are stored.
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext());

        File propertiesFile = ConfigurationHelper.findConfigurationFile("ec2-service.properties");
        Properties EC2Prop = null;

        if (null != propertiesFile) {
            logger.info("Use EC2 properties file: " + propertiesFile.getAbsolutePath());
            EC2Prop = new Properties();
            FileInputStream ec2PropFile = null;
            try {
                ec2PropFile = new FileInputStream(propertiesFile);
                EC2Prop.load(ec2PropFile);
            } catch (FileNotFoundException e) {
                logger.warn("Unable to open properties file: " + propertiesFile.getAbsolutePath(), e);
            } catch (IOException e) {
                logger.warn("Unable to read properties file: " + propertiesFile.getAbsolutePath(), e);
            } finally {
                IOUtils.closeQuietly(ec2PropFile);
            }
            String keystore = EC2Prop.getProperty("keystore");
            keystorePassword = EC2Prop.getProperty("keystorePass");
            wsdlVersion = EC2Prop.getProperty("WSDLVersion", "2012-08-15");
            version = EC2Prop.getProperty("cloudbridgeVersion", "UNKNOWN VERSION");

            String installedPath = System.getenv("CATALINA_HOME");
            if (installedPath == null)
                installedPath = System.getenv("CATALINA_BASE");
            if (installedPath == null)
                installedPath = System.getProperty("catalina.home");
            String webappPath = config.getServletContext().getRealPath("/");
            //pathToKeystore = new String( installedPath + File.separator + "webapps" + File.separator + webappName + File.separator + "WEB-INF" + File.separator + "classes" + File.separator + keystore );
            pathToKeystore = new String(webappPath + File.separator + "WEB-INF" + File.separator + "classes" + File.separator + keystore);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        doGetOrPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        doGetOrPost(req, resp);
    }

    protected void doGetOrPost(HttpServletRequest request, HttpServletResponse response) {

        if (debug) {
            System.out.println("EC2RestServlet.doGetOrPost: javax.servlet.forward.request_uri: " + request.getAttribute("javax.servlet.forward.request_uri"));
            System.out.println("EC2RestServlet.doGetOrPost: javax.servlet.forward.context_path: " + request.getAttribute("javax.servlet.forward.context_path"));
            System.out.println("EC2RestServlet.doGetOrPost: javax.servlet.forward.servlet_path: " + request.getAttribute("javax.servlet.forward.servlet_path"));
            System.out.println("EC2RestServlet.doGetOrPost: javax.servlet.forward.path_info: " + request.getAttribute("javax.servlet.forward.path_info"));
            System.out.println("EC2RestServlet.doGetOrPost: javax.servlet.forward.query_string: " + request.getAttribute("javax.servlet.forward.query_string"));

        }

        String action = request.getParameter("Action");
        logRequest(request);

        // -> unauthenticated calls, should still be done over HTTPS
        if (action.equalsIgnoreCase("SetUserKeys")) {
            setUserKeys(request, response);
            return;
        }

        if (action.equalsIgnoreCase("CloudEC2Version")) {
            cloudEC2Version(request, response);
            return;
        }

        // -> authenticated calls
        try {
            if (!authenticateRequest(request, response))
                return;

            if (action.equalsIgnoreCase("AllocateAddress"))
                allocateAddress(request, response);
            else if (action.equalsIgnoreCase("AssociateAddress"))
                associateAddress(request, response);
            else if (action.equalsIgnoreCase("AttachVolume"))
                attachVolume(request, response);
            else if (action.equalsIgnoreCase("AuthorizeSecurityGroupIngress"))
                authorizeSecurityGroupIngress(request, response);
            else if (action.equalsIgnoreCase("CreateImage"))
                createImage(request, response);
            else if (action.equalsIgnoreCase("CreateSecurityGroup"))
                createSecurityGroup(request, response);
            else if (action.equalsIgnoreCase("CreateSnapshot"))
                createSnapshot(request, response);
            else if (action.equalsIgnoreCase("CreateVolume"))
                createVolume(request, response);
            else if (action.equalsIgnoreCase("DeleteSecurityGroup"))
                deleteSecurityGroup(request, response);
            else if (action.equalsIgnoreCase("DeleteSnapshot"))
                deleteSnapshot(request, response);
            else if (action.equalsIgnoreCase("DeleteVolume"))
                deleteVolume(request, response);
            else if (action.equalsIgnoreCase("DeregisterImage"))
                deregisterImage(request, response);
            else if (action.equalsIgnoreCase("DescribeAddresses"))
                describeAddresses(request, response);
            else if (action.equalsIgnoreCase("DescribeAvailabilityZones"))
                describeAvailabilityZones(request, response);
            else if (action.equalsIgnoreCase("DescribeImageAttribute"))
                describeImageAttribute(request, response);
            else if (action.equalsIgnoreCase("DescribeImages"))
                describeImages(request, response);
            else if (action.equalsIgnoreCase("DescribeInstanceAttribute"))
                describeInstanceAttribute(request, response);
            else if (action.equalsIgnoreCase("DescribeInstances"))
                describeInstances(request, response);
            else if (action.equalsIgnoreCase("DescribeSecurityGroups"))
                describeSecurityGroups(request, response);
            else if (action.equalsIgnoreCase("DescribeSnapshots"))
                describeSnapshots(request, response);
            else if (action.equalsIgnoreCase("DescribeVolumes"))
                describeVolumes(request, response);
            else if (action.equalsIgnoreCase("DetachVolume"))
                detachVolume(request, response);
            else if (action.equalsIgnoreCase("DisassociateAddress"))
                disassociateAddress(request, response);
            else if (action.equalsIgnoreCase("ModifyImageAttribute"))
                modifyImageAttribute(request, response);
            else if (action.equalsIgnoreCase("ModifyInstanceAttribute"))
                modifyInstanceAttribute(request, response);
            else if (action.equalsIgnoreCase("RebootInstances"))
                rebootInstances(request, response);
            else if (action.equalsIgnoreCase("RegisterImage"))
                registerImage(request, response);
            else if (action.equalsIgnoreCase("ReleaseAddress"))
                releaseAddress(request, response);
            else if (action.equalsIgnoreCase("ResetImageAttribute"))
                resetImageAttribute(request, response);
            else if (action.equalsIgnoreCase("RevokeSecurityGroupIngress"))
                revokeSecurityGroupIngress(request, response);
            else if (action.equalsIgnoreCase("RunInstances"))
                runInstances(request, response);
            else if (action.equalsIgnoreCase("StartInstances"))
                startInstances(request, response);
            else if (action.equalsIgnoreCase("StopInstances"))
                stopInstances(request, response);
            else if (action.equalsIgnoreCase("TerminateInstances"))
                terminateInstances(request, response);
            else if (action.equalsIgnoreCase("SetCertificate"))
                setCertificate(request, response);
            else if (action.equalsIgnoreCase("DeleteCertificate"))
                deleteCertificate(request, response);
            else if (action.equalsIgnoreCase("SetOfferMapping"))
                setOfferMapping(request, response);
            else if (action.equalsIgnoreCase("DeleteOfferMapping"))
                deleteOfferMapping(request, response);
            else if (action.equalsIgnoreCase("CreateKeyPair"))
                createKeyPair(request, response);
            else if (action.equalsIgnoreCase("ImportKeyPair"))
                importKeyPair(request, response);
            else if (action.equalsIgnoreCase("DeleteKeyPair"))
                deleteKeyPair(request, response);
            else if (action.equalsIgnoreCase("DescribeKeyPairs"))
                describeKeyPairs(request, response);
            else if (action.equalsIgnoreCase("CreateTags"))
                createTags(request, response);
            else if (action.equalsIgnoreCase("DeleteTags"))
                deleteTags(request, response);
            else if (action.equalsIgnoreCase("DescribeTags"))
                describeTags(request, response);
            else if (action.equalsIgnoreCase("GetPasswordData"))
                getPasswordData(request, response);
            else {
                logger.error("Unsupported action " + action);
                throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available");
            }

        } catch (EC2ServiceException e) {
            response.setStatus(e.getErrorCode());

            if (e.getCause() != null && e.getCause() instanceof AxisFault) {
                String errorCode = ((AxisFault)e.getCause()).getFaultCode().getLocalPart();
                if (errorCode.startsWith("Client.")) // only in a SOAP API client error code is prefixed with Client.
                    errorCode = errorCode.split("Client.")[1];
                else if (errorCode.startsWith("Server.")) // only in a SOAP API server error code is prefixed with Server.
                    errorCode = errorCode.split("Server.")[1];
                faultResponse(response, errorCode, e.getMessage());
            } else {
                logger.error("EC2ServiceException: " + e.getMessage(), e);
                endResponse(response, e.toString());
            }
        } catch (PermissionDeniedException e) {
            logger.error("Unexpected exception: " + e.getMessage(), e);
            response.setStatus(403);
            endResponse(response, "Access denied");

        } catch (Exception e) {
            logger.error("Unexpected exception: " + e.getMessage(), e);
            response.setStatus(500);
            endResponse(response, e.toString());

        } finally {
            try {
                response.flushBuffer();
            } catch (IOException e) {
                logger.error("Unexpected exception " + e.getMessage(), e);
            }
        }
    }

    /**
     * Provide an easy way to determine the version of the implementation running.
     *
     * This is an unauthenticated REST call.
     */
    private void cloudEC2Version(HttpServletRequest request, HttpServletResponse response) {
        String version_response = new String("<?xml version=\"1.0\" encoding=\"utf-8\"?><CloudEC2Version>" + version + "</CloudEC2Version>");
        response.setStatus(200);
        endResponse(response, version_response);
    }

    /**
     * This request registers the Cloud.com account holder to the EC2 service.   The Cloud.com
     * account holder saves his API access and secret keys with the EC2 service so that
     * the EC2 service can make Cloud.com API calls on his behalf.   The given API access
     * and secret key are saved into the "usercredentials" database table.
     *
     * This is an unauthenticated REST call.   The only required parameters are 'accesskey' and
     * 'secretkey'.
     *
     * To verify that the given keys represent an existing account they are used to execute the
     * Cloud.com's listAccounts API function.   If the keys do not represent a valid account the
     * listAccounts function will fail.
     *
     * A user can call this REST function any number of times, on each call the Cloud.com secret
     * key is simply over writes any previously stored value.
     *
     * As with all REST calls HTTPS should be used to ensure their security.
     */
    private void setUserKeys(HttpServletRequest request, HttpServletResponse response) {
        String[] accessKey = null;
        String[] secretKey = null;
        TransactionLegacy txn = null;
        try {
            // -> all these parameters are required
            accessKey = request.getParameterValues("accesskey");
            if (null == accessKey || 0 == accessKey.length) {
                response.sendError(530, "Missing accesskey parameter");
                return;
            }

            secretKey = request.getParameterValues("secretkey");
            if (null == secretKey || 0 == secretKey.length) {
                response.sendError(530, "Missing secretkey parameter");
                return;
            }
        } catch (Exception e) {
            logger.error("SetUserKeys exception " + e.getMessage(), e);
            response.setStatus(500);
            endResponse(response, "SetUserKeys exception " + e.getMessage());
            return;
        }
        try {
            txn = TransactionLegacy.open(TransactionLegacy.AWSAPI_DB);
            txn.start();
            // -> use the keys to see if the account actually exists
            ServiceProvider.getInstance().getEC2Engine().validateAccount(accessKey[0], secretKey[0]);
            UserCredentialsVO user = new UserCredentialsVO(accessKey[0], secretKey[0]);
            ucDao.persist(user);
            txn.commit();
        } catch (Exception e) {
            logger.error("SetUserKeys " + e.getMessage(), e);
            response.setStatus(401);
            endResponse(response, e.toString());
            txn.close();
            return;
        }
        response.setStatus(200);
        endResponse(response, "User keys set successfully");
    }

    /**
     * The SOAP API for EC2 uses WS-Security to sign all client requests.  This requires that
     * the client have a public/private key pair and the public key defined by a X509 certificate.
     * Thus in order for a Cloud.com account holder to use the EC2's SOAP API he must register
     * his X509 certificate with the EC2 service.   This function allows the Cloud.com account
     * holder to "load" his X509 certificate into the service.   Note, that the SetUserKeys REST
     * function must be called before this call.
     *
     * This is an authenticated REST call and as such must contain all the required REST parameters
     * including: Signature, Timestamp, Expires, etc.   The signature is calculated using the
     * Cloud.com account holder's API access and secret keys and the Amazon defined EC2 signature
     * algorithm.
     *
     * A user can call this REST function any number of times, on each call the X509 certificate
     * simply over writes any previously stored value.
     */
    private void setCertificate(HttpServletRequest request, HttpServletResponse response) throws Exception {
        TransactionLegacy txn = null;
        try {
            // [A] Pull the cert and cloud AccessKey from the request
            String[] certificate = request.getParameterValues("cert");
            if (null == certificate || 0 == certificate.length) {
                response.sendError(530, "Missing cert parameter");
                return;
            }
            // logger.debug( "SetCertificate cert: [" + certificate[0] + "]" );

            String[] accessKey = request.getParameterValues("AWSAccessKeyId");
            if (null == accessKey || 0 == accessKey.length) {
                response.sendError(530, "Missing AWSAccessKeyId parameter");
                return;
            }

            // [B] Open our keystore
            FileInputStream fsIn = new FileInputStream(pathToKeystore);
            KeyStore certStore = KeyStore.getInstance("JKS");
            certStore.load(fsIn, keystorePassword.toCharArray());

            // -> use the Cloud API key to save the cert in the keystore
            // -> write the cert into the keystore on disk
            Certificate userCert = null;
            CertificateFactory cf = CertificateFactory.getInstance("X.509");

            ByteArrayInputStream bs = new ByteArrayInputStream(certificate[0].getBytes());
            while (bs.available() > 0)
                userCert = cf.generateCertificate(bs);
            certStore.setCertificateEntry(accessKey[0], userCert);

            FileOutputStream fsOut = new FileOutputStream(pathToKeystore);
            certStore.store(fsOut, keystorePassword.toCharArray());

            // [C] Associate the cert's uniqueId with the Cloud API keys
            String uniqueId = AuthenticationUtils.X509CertUniqueId(userCert);
            logger.debug("SetCertificate, uniqueId: " + uniqueId);
            txn = TransactionLegacy.open(TransactionLegacy.AWSAPI_DB);
            txn.start();
            UserCredentialsVO user = ucDao.getByAccessKey(accessKey[0]);
            user.setCertUniqueId(uniqueId);
            ucDao.update(user.getId(), user);
            response.setStatus(200);
            endResponse(response, "User certificate set successfully");
            txn.commit();

        } catch (NoSuchObjectException e) {
            logger.error("SetCertificate exception " + e.getMessage(), e);
            response.sendError(404, "SetCertificate exception " + e.getMessage());

        } catch (Exception e) {
            logger.error("SetCertificate exception " + e.getMessage(), e);
            response.sendError(500, "SetCertificate exception " + e.getMessage());
        } finally {
            txn.close();
        }

    }

    /**
     * The SOAP API for EC2 uses WS-Security to sign all client requests.  This requires that
     * the client have a public/private key pair and the public key defined by a X509 certificate.
     * This REST call allows a Cloud.com account holder to remove a previouly "loaded" X509
     * certificate out of the EC2 service.
     *
     * This is an unauthenticated REST call and as such must contain all the required REST parameters
     * including: Signature, Timestamp, Expires, etc.   The signature is calculated using the
     * Cloud.com account holder's API access and secret keys and the Amazon defined EC2 signature
     * algorithm.
     */
    private void deleteCertificate(HttpServletRequest request, HttpServletResponse response) throws Exception {
        TransactionLegacy txn = null;
        try {
            String[] accessKey = request.getParameterValues("AWSAccessKeyId");
            if (null == accessKey || 0 == accessKey.length) {
                response.sendError(530, "Missing AWSAccessKeyId parameter");
                return;
            }

            // -> delete the specified entry and save back to disk
            FileInputStream fsIn = new FileInputStream(pathToKeystore);
            KeyStore certStore = KeyStore.getInstance("JKS");
            certStore.load(fsIn, keystorePassword.toCharArray());

            if (certStore.containsAlias(accessKey[0])) {
                certStore.deleteEntry(accessKey[0]);
                FileOutputStream fsOut = new FileOutputStream(pathToKeystore);
                certStore.store(fsOut, keystorePassword.toCharArray());

                // -> dis-associate the cert's uniqueId with the Cloud API keys
                /*                  UserCredentialsDao credentialDao = new UserCredentialsDao();
                 credentialDao.setCertificateId( accessKey[0], null );

                 */txn = TransactionLegacy.open(TransactionLegacy.AWSAPI_DB);
                UserCredentialsVO user = ucDao.getByAccessKey(accessKey[0]);
                user.setCertUniqueId(null);
                ucDao.update(user.getId(), user);
                response.setStatus(200);
                endResponse(response, "User certificate deleted successfully");
                txn.commit();
            } else
                response.setStatus(404);

        } catch (NoSuchObjectException e) {
            logger.error("SetCertificate exception " + e.getMessage(), e);
            response.sendError(404, "SetCertificate exception " + e.getMessage());

        } catch (Exception e) {
            logger.error("DeleteCertificate exception " + e.getMessage(), e);
            response.sendError(500, "DeleteCertificate exception " + e.getMessage());
        } finally {
            txn.close();
        }
    }

    /**
     * Allow the caller to define the mapping between the Amazon instance type strings
     * (e.g., m1.small, cc1.4xlarge) and the cloudstack service offering ids.  Setting
     * an existing mapping just over writes the prevous values.
     */
    private void setOfferMapping(HttpServletRequest request, HttpServletResponse response) {
        String amazonOffer = null;
        String cloudOffer = null;

        try {
            // -> all these parameters are required
            amazonOffer = request.getParameter("amazonoffer");
            if (null == amazonOffer) {
                response.sendError(530, "Missing amazonoffer parameter");
                return;
            }

            cloudOffer = request.getParameter("cloudoffer");
            if (null == cloudOffer) {
                response.sendError(530, "Missing cloudoffer parameter");
                return;
            }
        } catch (Exception e) {
            logger.error("SetOfferMapping exception " + e.getMessage(), e);
            response.setStatus(500);
            endResponse(response, "SetOfferMapping exception " + e.getMessage());
            return;
        }

        // validate account is admin level
        try {
            CloudStackAccount currentAccount = ServiceProvider.getInstance().getEC2Engine().getCurrentAccount();

            if (currentAccount.getAccountType() != 1) {
                logger.debug("SetOfferMapping called by non-admin user!");
                response.setStatus(500);
                endResponse(response, "Permission denied for non-admin user to setOfferMapping!");
                return;
            }
        } catch (Exception e) {
            logger.error("SetOfferMapping " + e.getMessage(), e);
            response.setStatus(401);
            endResponse(response, e.toString());
            return;
        }

        try {

            ofDao.setOfferMapping(amazonOffer, cloudOffer);

        } catch (Exception e) {
            logger.error("SetOfferMapping " + e.getMessage(), e);
            response.setStatus(401);
            endResponse(response, e.toString());
            return;
        }
        response.setStatus(200);
        endResponse(response, "offering mapping set successfully");
    }

    private void deleteOfferMapping(HttpServletRequest request, HttpServletResponse response) {
        String amazonOffer = null;

        try {
            // -> all these parameters are required
            amazonOffer = request.getParameter("amazonoffer");
            if (null == amazonOffer) {
                response.sendError(530, "Missing amazonoffer parameter");
                return;
            }

        } catch (Exception e) {
            logger.error("DeleteOfferMapping exception " + e.getMessage(), e);
            response.setStatus(500);
            endResponse(response, "DeleteOfferMapping exception " + e.getMessage());
            return;
        }

        // validate account is admin level
        try {
            CloudStackAccount currentAccount = ServiceProvider.getInstance().getEC2Engine().getCurrentAccount();

            if (currentAccount.getAccountType() != 1) {
                logger.debug("deleteOfferMapping called by non-admin user!");
                response.setStatus(500);
                endResponse(response, "Permission denied for non-admin user to deleteOfferMapping!");
                return;
            }
        } catch (Exception e) {
            logger.error("deleteOfferMapping " + e.getMessage(), e);
            response.setStatus(401);
            endResponse(response, e.toString());
            return;
        }

        try {
            ofDao.deleteOfferMapping(amazonOffer);
        } catch (Exception e) {
            logger.error("DeleteOfferMapping " + e.getMessage(), e);
            response.setStatus(401);
            endResponse(response, e.toString());
            return;
        }
        response.setStatus(200);
        endResponse(response, "offering mapping deleted successfully");
    }

    /**
     * The approach taken here is to map these REST calls into the same objects used
     * to implement the matching SOAP requests (e.g., AttachVolume).   This is done by parsing
     * out the URL parameters and loading them into the relevant EC2XXX object(s).   Once
     * the parameters are loaded the appropriate EC2Engine function is called to perform
     * the requested action.   The result of the EC2Engine function is a standard
     * Amazon WSDL defined object (e.g., AttachVolumeResponse Java object).   Finally the
     * serialize method is called on the returned response object to obtain the extected
     * response XML.
     */
    private void attachVolume(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Volume EC2request = new EC2Volume();

        // -> all these parameters are required
        String[] volumeId = request.getParameterValues("VolumeId");
        if (null != volumeId && 0 < volumeId.length)
            EC2request.setId(volumeId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - VolumeId");
        }

        String[] instanceId = request.getParameterValues("InstanceId");
        if (null != instanceId && 0 < instanceId.length)
            EC2request.setInstanceId(instanceId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        String[] device = request.getParameterValues("Device");
        if (null != device && 0 < device.length)
            EC2request.setDevice(device[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Device");
        }

        // -> execute the request
        AttachVolumeResponse EC2response = EC2SoapServiceImpl.toAttachVolumeResponse(ServiceProvider.getInstance().getEC2Engine().attachVolume(EC2request));
        serializeResponse(response, EC2response);
    }

    /**
     * The SOAP equivalent of this function appears to allow multiple permissions per request, yet
     * in the REST API documentation only one permission is allowed.
     */
    private void revokeSecurityGroupIngress(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2AuthorizeRevokeSecurityGroup EC2request = new EC2AuthorizeRevokeSecurityGroup();

        String[] groupName = request.getParameterValues("GroupName");
        if (null != groupName && 0 < groupName.length)
            EC2request.setName(groupName[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - GroupName");
        }

        // -> not clear how many parameters there are until we fail to get IpPermissions.n.IpProtocol
        int nCount = 1, mCount;
        do {
            EC2IpPermission perm = new EC2IpPermission();

            String[] protocol = request.getParameterValues("IpPermissions." + nCount + ".IpProtocol");
            if (null != protocol && 0 < protocol.length)
                perm.setProtocol(protocol[0]);
            else
                break;

            String[] fromPort = request.getParameterValues("IpPermissions." + nCount + ".FromPort");
            if (null != fromPort && 0 < fromPort.length) {
                if (protocol[0].equalsIgnoreCase("icmp"))
                    perm.setIcmpType(fromPort[0]);
                else
                    perm.setFromPort(Integer.parseInt(fromPort[0]));
            }

            String[] toPort = request.getParameterValues("IpPermissions." + nCount + ".ToPort");
            if (null != toPort && 0 < toPort.length) {
                if (protocol[0].equalsIgnoreCase("icmp"))
                    perm.setIcmpCode(toPort[0]);
                else
                    perm.setToPort(Integer.parseInt(toPort[0]));
            }

            // -> list: IpPermissions.n.IpRanges.m.CidrIp
            mCount = 1;
            do {
                String[] ranges = request.getParameterValues("IpPermissions." + nCount + ".IpRanges." + mCount + ".CidrIp");
                if (null != ranges && 0 < ranges.length)
                    perm.addIpRange(ranges[0]);
                else
                    break;
                mCount++;
            } while (true);

            // -> list: IpPermissions.n.Groups.m.UserId and IpPermissions.n.Groups.m.GroupName
            mCount = 1;
            do {
                EC2SecurityGroup group = new EC2SecurityGroup();

                String[] user = request.getParameterValues("IpPermissions." + nCount + ".Groups." + mCount + ".UserId");
                if (null != user && 0 < user.length)
                    group.setAccount(user[0]);
                else
                    break;

                String[] name = request.getParameterValues("IpPermissions." + nCount + ".Groups." + mCount + ".GroupName");
                if (null != name && 0 < name.length)
                    group.setName(name[0]);
                else
                    break;

                perm.addUser(group);
                mCount++;
            } while (true);

            // -> multiple IP permissions can be specified per group name
            EC2request.addIpPermission(perm);
            nCount++;
        } while (true);

        if (1 == nCount) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - IpPermissions");
        }

        // -> execute the request
        RevokeSecurityGroupIngressResponse EC2response =
            EC2SoapServiceImpl.toRevokeSecurityGroupIngressResponse(ServiceProvider.getInstance().getEC2Engine().revokeSecurityGroup(EC2request));
        serializeResponse(response, EC2response);
    }

    private void authorizeSecurityGroupIngress(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        // -> parse the complicated paramters into our standard object
        EC2AuthorizeRevokeSecurityGroup EC2request = new EC2AuthorizeRevokeSecurityGroup();

        String[] groupName = request.getParameterValues("GroupName");
        if (null != groupName && 0 < groupName.length)
            EC2request.setName(groupName[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter 'Groupname'");
        }

        // -> not clear how many parameters there are until we fail to get IpPermissions.n.IpProtocol
        int nCount = 1;
        do {
            EC2IpPermission perm = new EC2IpPermission();

            String[] protocol = request.getParameterValues("IpPermissions." + nCount + ".IpProtocol");
            if (null != protocol && 0 < protocol.length)
                perm.setProtocol(protocol[0]);
            else
                break;

            String[] fromPort = request.getParameterValues("IpPermissions." + nCount + ".FromPort");
            if (null != fromPort && 0 < fromPort.length) {
                if (protocol[0].equalsIgnoreCase("icmp"))
                    perm.setIcmpType(fromPort[0]);
                else
                    perm.setFromPort(Integer.parseInt(fromPort[0]));
            }

            String[] toPort = request.getParameterValues("IpPermissions." + nCount + ".ToPort");
            if (null != toPort && 0 < toPort.length) {
                if (protocol[0].equalsIgnoreCase("icmp"))
                    perm.setIcmpCode(toPort[0]);
                else
                    perm.setToPort(Integer.parseInt(toPort[0]));
            }

            // -> list: IpPermissions.n.IpRanges.m.CidrIp
            int mCount = 1;
            do {
                String[] ranges = request.getParameterValues("IpPermissions." + nCount + ".IpRanges." + mCount + ".CidrIp");
                if (null != ranges && 0 < ranges.length)
                    perm.addIpRange(ranges[0]);
                else
                    break;
                mCount++;

            } while (true);

            // -> list: IpPermissions.n.Groups.m.UserId and IpPermissions.n.Groups.m.GroupName
            mCount = 1;
            do {
                String[] user = request.getParameterValues("IpPermissions." + nCount + ".Groups." + mCount + ".UserId");
                if (null == user || 0 == user.length)
                    break;

                String[] name = request.getParameterValues("IpPermissions." + nCount + ".Groups." + mCount + ".GroupName");
                if (null == name || 0 == name.length)
                    break;

                EC2SecurityGroup group = new EC2SecurityGroup();
                group.setAccount(user[0]);
                group.setName(name[0]);
                perm.addUser(group);
                mCount++;

            } while (true);

            // -> multiple IP permissions can be specified per group name
            EC2request.addIpPermission(perm);
            nCount++;

        } while (true);

        if (1 == nCount) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - IpPermissions");
        }

        // -> execute the request
        AuthorizeSecurityGroupIngressResponse EC2response =
            EC2SoapServiceImpl.toAuthorizeSecurityGroupIngressResponse(ServiceProvider.getInstance().getEC2Engine().authorizeSecurityGroup(EC2request));
        serializeResponse(response, EC2response);
    }

    private void detachVolume(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Volume EC2request = new EC2Volume();

        String[] volumeId = request.getParameterValues("VolumeId");
        if (null != volumeId && 0 < volumeId.length)
            EC2request.setId(volumeId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter 'VolumeId'");
        }

        String[] instanceId = request.getParameterValues("InstanceId");
        if (null != instanceId && 0 < instanceId.length)
            EC2request.setInstanceId(instanceId[0]);

        String[] device = request.getParameterValues("Device");
        if (null != device && 0 < device.length)
            EC2request.setDevice(device[0]);

        // -> execute the request
        DetachVolumeResponse EC2response = EC2SoapServiceImpl.toDetachVolumeResponse(ServiceProvider.getInstance().getEC2Engine().detachVolume(EC2request));
        serializeResponse(response, EC2response);
    }

    private void deleteVolume(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Volume EC2request = new EC2Volume();

        String[] volumeId = request.getParameterValues("VolumeId");
        if (null != volumeId && 0 < volumeId.length)
            EC2request.setId(volumeId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - VolumeId");
        }

        // -> execute the request
        DeleteVolumeResponse EC2response = EC2SoapServiceImpl.toDeleteVolumeResponse(ServiceProvider.getInstance().getEC2Engine().deleteVolume(EC2request));
        serializeResponse(response, EC2response);
    }

    private void createVolume(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2CreateVolume EC2request = new EC2CreateVolume();

        String[] zoneName = request.getParameterValues("AvailabilityZone");
        if (null != zoneName && 0 < zoneName.length)
            EC2request.setZoneName(zoneName[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing parameter - AvailabilityZone");
        }

        String[] size = request.getParameterValues("Size");
        String[] snapshotId = request.getParameterValues("SnapshotId");
        boolean useSnapshot = false;
        boolean useSize = false;

        if (null != size && 0 < size.length)
            useSize = true;

        if (snapshotId != null && snapshotId.length != 0)
            useSnapshot = true;

        if (useSize && !useSnapshot) {
            EC2request.setSize(size[0]);
        } else if (useSnapshot && !useSize) {
            EC2request.setSnapshotId(snapshotId[0]);
        } else if (useSize && useSnapshot) {
            throw new EC2ServiceException(ClientError.InvalidParameterCombination, "Parameters 'Size' and 'SnapshotId' are mutually exclusive");
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Parameter 'Size' or 'SnapshotId' has to be specified");
        }

        // -> execute the request
        CreateVolumeResponse EC2response = EC2SoapServiceImpl.toCreateVolumeResponse(ServiceProvider.getInstance().getEC2Engine().createVolume(EC2request));
        serializeResponse(response, EC2response);
    }

    private void createSecurityGroup(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {

        String groupName, groupDescription = null;

        String[] name = request.getParameterValues("GroupName");
        if (null != name && 0 < name.length)
            groupName = name[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - GroupName");
        }
        String[] desc = request.getParameterValues("GroupDescription");
        if (null != desc && 0 < desc.length)
            groupDescription = desc[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - GroupDescription");
        }

        // -> execute the request
        CreateSecurityGroupResponse EC2response =
            EC2SoapServiceImpl.toCreateSecurityGroupResponse(ServiceProvider.getInstance().getEC2Engine().createSecurityGroup(groupName, groupDescription));
        serializeResponse(response, EC2response);
    }

    private void deleteSecurityGroup(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        String groupName = null;

        String[] name = request.getParameterValues("GroupName");
        if (null != name && 0 < name.length)
            groupName = name[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - GroupName");
        }

        // -> execute the request
        DeleteSecurityGroupResponse EC2response =
            EC2SoapServiceImpl.toDeleteSecurityGroupResponse(ServiceProvider.getInstance().getEC2Engine().deleteSecurityGroup(groupName));
        serializeResponse(response, EC2response);
    }

    private void deleteSnapshot(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        String snapshotId = null;

        String[] snapSet = request.getParameterValues("SnapshotId");
        if (null != snapSet && 0 < snapSet.length)
            snapshotId = snapSet[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - SnapshotId");
        }

        // -> execute the request
        DeleteSnapshotResponse EC2response = EC2SoapServiceImpl.toDeleteSnapshotResponse(ServiceProvider.getInstance().getEC2Engine().deleteSnapshot(snapshotId));
        serializeResponse(response, EC2response);
    }

    private void createSnapshot(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        String volumeId = null;

        String[] volSet = request.getParameterValues("VolumeId");
        if (null != volSet && 0 < volSet.length)
            volumeId = volSet[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - VolumeId");
        }

        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        CreateSnapshotResponse EC2response = EC2SoapServiceImpl.toCreateSnapshotResponse(engine.createSnapshot(volumeId), engine);
        serializeResponse(response, EC2response);
    }

    private void deregisterImage(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Image image = new EC2Image();

        String[] imageId = request.getParameterValues("ImageId");
        if (null != imageId && 0 < imageId.length)
            image.setId(imageId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ImageId");
        }

        // -> execute the request
        DeregisterImageResponse EC2response = EC2SoapServiceImpl.toDeregisterImageResponse(ServiceProvider.getInstance().getEC2Engine().deregisterImage(image));
        serializeResponse(response, EC2response);
    }

    private void createImage(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2CreateImage EC2request = new EC2CreateImage();

        String[] instanceId = request.getParameterValues("InstanceId");
        if (null != instanceId && 0 < instanceId.length)
            EC2request.setInstanceId(instanceId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        String[] name = request.getParameterValues("Name");
        if (null != name && 0 < name.length)
            EC2request.setName(name[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Name");
        }

        String[] description = request.getParameterValues("Description");
        if (null != description && 0 < description.length) {
            if (description[0].length() > 255)
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Length of the value of parameter Description should be less than 255");
            EC2request.setDescription(description[0]);
        }

        // -> execute the request
        CreateImageResponse EC2response = EC2SoapServiceImpl.toCreateImageResponse(ServiceProvider.getInstance().getEC2Engine().createImage(EC2request));
        serializeResponse(response, EC2response);
    }

    private void registerImage(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2RegisterImage EC2request = new EC2RegisterImage();

        String[] location = request.getParameterValues("ImageLocation");
        if (null != location && 0 < location.length)
            EC2request.setLocation(location[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing parameter - ImageLocation");
        }

        String[] cloudRedfined = request.getParameterValues("Architecture");
        if (null != cloudRedfined && 0 < cloudRedfined.length)
            EC2request.setArchitecture(cloudRedfined[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Architecture");
        }

        String[] name = request.getParameterValues("Name");
        if (null != name && 0 < name.length)
            EC2request.setName(name[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Name");
        }

        String[] description = request.getParameterValues("Description");
        if (null != description && 0 < description.length)
            EC2request.setDescription(description[0]);

        // -> execute the request
        RegisterImageResponse EC2response = EC2SoapServiceImpl.toRegisterImageResponse(ServiceProvider.getInstance().getEC2Engine().registerImage(EC2request));
        serializeResponse(response, EC2response);
    }

    private void modifyImageAttribute(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2ModifyImageAttribute ec2request = new EC2ModifyImageAttribute();

        String[] imageId = request.getParameterValues("ImageId");
        if (imageId != null && imageId.length > 0)
            ec2request.setImageId(imageId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ImageId");
        }

        String[] description = request.getParameterValues("Description.Value");
        if (description != null && description.length > 0) {
            ec2request.setAttribute(ImageAttribute.description);
            ec2request.setDescription(description[0]);
        } else {
            //add all launch permissions to ec2request
            ec2request = addLaunchPermImageAttribute(request, ec2request);
            if (ec2request.getLaunchPermissionSet().length > 0)
                ec2request.setAttribute(ImageAttribute.launchPermission);
            else {
                throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Description/LaunchPermission should be provided");
            }
        }

        // -> execute the request
        ModifyImageAttributeResponse EC2response =
            EC2SoapServiceImpl.toModifyImageAttributeResponse(ServiceProvider.getInstance().getEC2Engine().modifyImageAttribute(ec2request));
        serializeResponse(response, EC2response);
    }

    private EC2ModifyImageAttribute addLaunchPermImageAttribute(HttpServletRequest request, EC2ModifyImageAttribute ec2request) {
        String[] users = {".UserId", ".Group"};
        String[] operations = {"LaunchPermission.Add.", "LaunchPermission.Remove."};
        int nCount = 1;

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                List<String> launchPermissionList = new ArrayList<String>();
                do {
                    String[] launchPermissionAddGroup = request.getParameterValues(operations[j] + nCount + users[i]);
                    if (launchPermissionAddGroup != null && launchPermissionAddGroup.length > 0)
                        launchPermissionList.add(launchPermissionAddGroup[0]);
                    else
                        break;
                    nCount++;
                } while (true);
                if (nCount != 1) {
                    EC2ImageLaunchPermission ec2LaunchPermission = new EC2ImageLaunchPermission();
                    if (operations[j].contains("Add"))
                        ec2LaunchPermission.setLaunchPermOp(EC2ImageLaunchPermission.Operation.add);
                    else
                        ec2LaunchPermission.setLaunchPermOp(EC2ImageLaunchPermission.Operation.remove);
                    for (String launchPerm : launchPermissionList) {
                        ec2LaunchPermission.addLaunchPermission(launchPerm);
                    }
                    ec2request.addLaunchPermission(ec2LaunchPermission);
                    nCount = 1;
                }
            }
        }

        return ec2request;
    }

    private void resetImageAttribute(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2ModifyImageAttribute ec2request = new EC2ModifyImageAttribute();

        String[] imageId = request.getParameterValues("ImageId");
        if (imageId != null && imageId.length > 0)
            ec2request.setImageId(imageId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ImageId");
        }

        String[] attribute = request.getParameterValues("Attribute");
        if (attribute != null && attribute.length > 0) {
            if (attribute[0].equalsIgnoreCase("launchPermission"))
                ec2request.setAttribute(ImageAttribute.launchPermission);
            else {
                throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Description/LaunchPermission should be provided");
            }
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Attribute");
        }

        EC2ImageLaunchPermission launchPermission = new EC2ImageLaunchPermission();
        launchPermission.setLaunchPermOp(EC2ImageLaunchPermission.Operation.reset);
        ec2request.addLaunchPermission(launchPermission);

        // -> execute the request
        ResetImageAttributeResponse EC2response =
            EC2SoapServiceImpl.toResetImageAttributeResponse(ServiceProvider.getInstance().getEC2Engine().modifyImageAttribute(ec2request));
        serializeResponse(response, EC2response);
    }

    private void runInstances(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2RunInstances EC2request = new EC2RunInstances();

        // -> so in the Amazon docs for this REST call there is no userData even though there is in the SOAP docs
        String[] imageId = request.getParameterValues("ImageId");
        if (null != imageId && 0 < imageId.length)
            EC2request.setTemplateId(imageId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ImageId");
        }

        String[] minCount = request.getParameterValues("MinCount");
        if (minCount == null || minCount.length < 1) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - MinCount");
        } else if (Integer.parseInt(minCount[0]) < 1) {
            throw new EC2ServiceException(ClientError.InvalidParameterValue, "Value of parameter MinCount should be greater than 0");
        } else {
            EC2request.setMinCount(Integer.parseInt(minCount[0]));
        }

        String[] maxCount = request.getParameterValues("MaxCount");
        if (maxCount == null || maxCount.length < 1) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - MaxCount");
        } else if (Integer.parseInt(maxCount[0]) < 1) {
            throw new EC2ServiceException(ClientError.InvalidParameterValue, "Value of parameter MaxCount should be greater than 0");
        } else {
            EC2request.setMaxCount(Integer.parseInt(maxCount[0]));
        }

        String[] instanceType = request.getParameterValues("InstanceType");
        if (null != instanceType && 0 < instanceType.length)
            EC2request.setInstanceType(instanceType[0]);

        String[] zoneName = request.getParameterValues("Placement.AvailabilityZone");
        if (null != zoneName && 0 < zoneName.length)
            EC2request.setZoneName(zoneName[0]);

        String[] size = request.getParameterValues("size");
        if (size != null) {
            EC2request.setSize(Integer.valueOf(size[0]));
        }

        String[] keyName = request.getParameterValues("KeyName");
        if (keyName != null) {
            EC2request.setKeyName(keyName[0]);
        }

        String[] userData = request.getParameterValues("UserData");
        if (userData != null) {
            EC2request.setUserData(userData[0]);
        }

        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("SecurityGroup")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length) {
                    if (key.startsWith("SecurityGroupId"))
                        EC2request.addSecuritGroupId(value[0]);
                    else
                        EC2request.addSecuritGroupName(value[0]);
                }
            }
        }

        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        RunInstancesResponse EC2response = EC2SoapServiceImpl.toRunInstancesResponse(engine.runInstances(EC2request), engine);
        serializeResponse(response, EC2response);
    }

    private void rebootInstances(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2RebootInstances EC2request = new EC2RebootInstances();
        int count = 0;

        // -> load in all the "InstanceId.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("InstanceId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length) {
                    EC2request.addInstanceId(value[0]);
                    count++;
                }
            }
        }
        if (0 == count) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        // -> execute the request
        RebootInstancesResponse EC2response = EC2SoapServiceImpl.toRebootInstancesResponse(ServiceProvider.getInstance().getEC2Engine().rebootInstances(EC2request));
        serializeResponse(response, EC2response);
    }

    private void startInstances(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2StartInstances EC2request = new EC2StartInstances();
        int count = 0;

        // -> load in all the "InstanceId.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("InstanceId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length) {
                    EC2request.addInstanceId(value[0]);
                    count++;
                }
            }
        }
        if (0 == count) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        // -> execute the request
        StartInstancesResponse EC2response = EC2SoapServiceImpl.toStartInstancesResponse(ServiceProvider.getInstance().getEC2Engine().startInstances(EC2request));
        serializeResponse(response, EC2response);
    }

    private void stopInstances(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2StopInstances EC2request = new EC2StopInstances();
        int count = 0;

        // -> load in all the "InstanceId.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("InstanceId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length) {
                    EC2request.addInstanceId(value[0]);
                    count++;
                }
            }
        }
        if (0 == count) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        String[] force = request.getParameterValues("Force");
        if (force != null) {
            EC2request.setForce(Boolean.parseBoolean(force[0]));
        }

        // -> execute the request
        StopInstancesResponse EC2response = EC2SoapServiceImpl.toStopInstancesResponse(ServiceProvider.getInstance().getEC2Engine().stopInstances(EC2request));
        serializeResponse(response, EC2response);
    }

    private void terminateInstances(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2StopInstances EC2request = new EC2StopInstances();
        int count = 0;

        // -> load in all the "InstanceId.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("InstanceId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length) {
                    EC2request.addInstanceId(value[0]);
                    count++;
                }
            }
        }
        if (0 == count) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        // -> execute the request
        EC2request.setDestroyInstances(true);
        TerminateInstancesResponse EC2response = EC2SoapServiceImpl.toTermInstancesResponse(ServiceProvider.getInstance().getEC2Engine().stopInstances(EC2request));
        serializeResponse(response, EC2response);
    }

    /**
     * We are reusing the SOAP code to process this request.   We then use Axiom to serialize the
     * resulting EC2 Amazon object into XML to return to the client.
     */
    private void describeAvailabilityZones(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeAvailabilityZones EC2request = new EC2DescribeAvailabilityZones();

        // -> load in all the "ZoneName.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("ZoneName")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    EC2request.addZone(value[0]);
            }
        }

        // add filters
        EC2Filter[] filterSet = extractFilters(request);
        if (filterSet != null) {
            EC2AvailabilityZonesFilterSet afs = new EC2AvailabilityZonesFilterSet();
            for (int i = 0; i < filterSet.length; i++) {
                afs.addFilter(filterSet[i]);
            }
            EC2request.setFilterSet(afs);
        }

        // -> execute the request
        DescribeAvailabilityZonesResponse EC2response =
            EC2SoapServiceImpl.toDescribeAvailabilityZonesResponse(ServiceProvider.getInstance().getEC2Engine().describeAvailabilityZones(EC2request));
        serializeResponse(response, EC2response);
    }

    private void describeImages(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeImages EC2request = new EC2DescribeImages();

        // -> load in all the "ImageId.n" parameters if any, and ignore all other parameters
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("ImageId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    EC2request.addImageSet(value[0]);
            }
        }
        // add filters
        EC2Filter[] filterSet = extractFilters(request);
        if (filterSet != null) {
            EC2ImageFilterSet ifs = new EC2ImageFilterSet();
            for (int i = 0; i < filterSet.length; i++) {
                ifs.addFilter(filterSet[i]);
            }
            EC2request.setFilterSet(ifs);
        }
        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        DescribeImagesResponse EC2response = EC2SoapServiceImpl.toDescribeImagesResponse(engine.describeImages(EC2request));
        serializeResponse(response, EC2response);
    }

    private void describeImageAttribute(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeImageAttribute ec2request = new EC2DescribeImageAttribute();

        String[] imageId = request.getParameterValues("ImageId");
        if (imageId != null && imageId.length > 0)
            ec2request.setImageId(imageId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ImageId");
        }

        String[] attribute = request.getParameterValues("Attribute");
        if (attribute != null && attribute.length > 0) {
            if (attribute[0].equalsIgnoreCase("description"))
                ec2request.setAttribute(ImageAttribute.description);
            else if (attribute[0].equalsIgnoreCase("launchPermission"))
                ec2request.setAttribute(ImageAttribute.launchPermission);
            else {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Only values supported for paramter Attribute are - Description/LaunchPermission");
            }
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Attribute");
        }

        DescribeImageAttributeResponse EC2response =
            EC2SoapServiceImpl.toDescribeImageAttributeResponse(ServiceProvider.getInstance().getEC2Engine().describeImageAttribute(ec2request));
        serializeResponse(response, EC2response);
    }

    private void describeInstances(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeInstances EC2request = new EC2DescribeInstances();

        // -> load in all the "InstanceId.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("InstanceId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    EC2request.addInstanceId(value[0]);
            }
        }

        // -> are there any filters with this request?
        EC2Filter[] filterSet = extractFilters(request);
        if (null != filterSet) {
            EC2InstanceFilterSet ifs = new EC2InstanceFilterSet();
            for (int i = 0; i < filterSet.length; i++)
                ifs.addFilter(filterSet[i]);
            EC2request.setFilterSet(ifs);
        }

        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        DescribeInstancesResponse EC2response = EC2SoapServiceImpl.toDescribeInstancesResponse(engine.describeInstances(EC2request), engine);
        serializeResponse(response, EC2response);
    }

    private void describeAddresses(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeAddresses ec2Request = new EC2DescribeAddresses();

        // -> load in all the "PublicIp.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("PublicIp")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    ec2Request.addPublicIp(value[0]);
            }
        }

        // add filters
        EC2Filter[] filterSet = extractFilters(request);
        if (filterSet != null) {
            EC2AddressFilterSet afs = new EC2AddressFilterSet();
            for (int i = 0; i < filterSet.length; i++)
                afs.addFilter(filterSet[i]);
            ec2Request.setFilterSet(afs);
        }
        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        serializeResponse(response, EC2SoapServiceImpl.toDescribeAddressesResponse(engine.describeAddresses(ec2Request)));
    }

    private void allocateAddress(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {

        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();

        AllocateAddressResponse ec2Response = EC2SoapServiceImpl.toAllocateAddressResponse(engine.allocateAddress());

        serializeResponse(response, ec2Response);
    }

    private void releaseAddress(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {

        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();

        String publicIp = request.getParameter("PublicIp");
        if (publicIp == null) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - PublicIp");
        }

        EC2ReleaseAddress ec2Request = new EC2ReleaseAddress();
        if (ec2Request != null) {
            ec2Request.setPublicIp(publicIp);
        }

        ReleaseAddressResponse EC2Response = EC2SoapServiceImpl.toReleaseAddressResponse(engine.releaseAddress(ec2Request));

        serializeResponse(response, EC2Response);
    }

    private void associateAddress(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();

        String publicIp = request.getParameter("PublicIp");
        if (null == publicIp) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - PublicIp");
        }
        String instanceId = request.getParameter("InstanceId");
        if (null == instanceId) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        EC2AssociateAddress ec2Request = new EC2AssociateAddress();
        if (ec2Request != null) {
            ec2Request.setInstanceId(instanceId);
            ec2Request.setPublicIp(publicIp);
        }

        AssociateAddressResponse ec2Response = EC2SoapServiceImpl.toAssociateAddressResponse(engine.associateAddress(ec2Request));

        serializeResponse(response, ec2Response);
    }

    private void disassociateAddress(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();

        String publicIp = request.getParameter("PublicIp");
        if (null == publicIp) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - PublicIp");
        }

        EC2DisassociateAddress ec2Request = new EC2DisassociateAddress();
        if (ec2Request != null) {
            ec2Request.setPublicIp(publicIp);
        }

        DisassociateAddressResponse ec2Response = EC2SoapServiceImpl.toDisassociateAddressResponse(engine.disassociateAddress(ec2Request));

        serializeResponse(response, ec2Response);
    }

    private void describeSecurityGroups(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeSecurityGroups EC2request = new EC2DescribeSecurityGroups();

        // -> load in all the "GroupName.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("GroupName")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    EC2request.addGroupName(value[0]);
            }
        }

        // -> are there any filters with this request?
        EC2Filter[] filterSet = extractFilters(request);
        if (null != filterSet) {
            EC2GroupFilterSet gfs = new EC2GroupFilterSet();
            for (EC2Filter filter : filterSet)
                gfs.addFilter(filter);
            EC2request.setFilterSet(gfs);
        }

        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();

        DescribeSecurityGroupsResponse EC2response = EC2SoapServiceImpl.toDescribeSecurityGroupsResponse(engine.describeSecurityGroups(EC2request));
        serializeResponse(response, EC2response);
    }

    private void describeInstanceAttribute(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeInstances EC2request = new EC2DescribeInstances();
        String[] instanceId = request.getParameterValues("InstanceId");
        if (instanceId != null && instanceId.length > 0)
            EC2request.addInstanceId(instanceId[0]);
        else
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");

        String[] attribute = request.getParameterValues("Attribute");
        if (attribute != null && attribute.length > 0) {
            if (!attribute[0].equalsIgnoreCase("instanceType")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Only value supported for paramter Attribute is 'instanceType'");
            }
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Attribute");
        }

        // -> execute the request
        DescribeInstanceAttributeResponse EC2response =
            EC2SoapServiceImpl.toDescribeInstanceAttributeResponse(ServiceProvider.getInstance().getEC2Engine().describeInstances(EC2request));
        serializeResponse(response, EC2response);
    }

    private void modifyInstanceAttribute(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2ModifyInstanceAttribute ec2Request = new EC2ModifyInstanceAttribute();

        String[] instanceId = request.getParameterValues("InstanceId");
        if (instanceId != null && instanceId.length > 0)
            ec2Request.setInstanceId(instanceId[0]);
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        String[] instanceType = request.getParameterValues("InstanceType.Value");
        String[] userData = request.getParameterValues("UserData.Value");

        if (instanceType != null && userData != null) {
            throw new EC2ServiceException(ClientError.InvalidParameterCombination, "Only one attribute can be" + " specified at a time");
        }
        if (instanceType != null && instanceType.length > 0) {
            ec2Request.setInstanceType(instanceType[0]);
        } else if (userData != null && userData.length > 0) {
            ec2Request.setUserData(userData[0]);
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing parameter - InstanceType/UserData should be provided");
        }

        // -> execute the request
        ModifyInstanceAttributeResponse EC2response =
            EC2SoapServiceImpl.toModifyInstanceAttributeResponse(ServiceProvider.getInstance().getEC2Engine().modifyInstanceAttribute(ec2Request));
        serializeResponse(response, EC2response);
    }

    private void describeSnapshots(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeSnapshots EC2request = new EC2DescribeSnapshots();

        // -> load in all the "SnapshotId.n" parameters if any, and ignore any other parameters
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("SnapshotId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    EC2request.addSnapshotId(value[0]);
            }
        }

        // -> are there any filters with this request?
        EC2Filter[] filterSet = extractFilters(request);
        if (null != filterSet) {
            EC2SnapshotFilterSet sfs = new EC2SnapshotFilterSet();
            for (int i = 0; i < filterSet.length; i++)
                sfs.addFilter(filterSet[i]);
            EC2request.setFilterSet(sfs);
        }

        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        DescribeSnapshotsResponse EC2response = EC2SoapServiceImpl.toDescribeSnapshotsResponse(engine.describeSnapshots(EC2request));
        serializeResponse(response, EC2response);
    }

    private void describeVolumes(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeVolumes EC2request = new EC2DescribeVolumes();

        // -> load in all the "VolumeId.n" parameters if any
        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("VolumeId")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    EC2request.addVolumeId(value[0]);
            }
        }

        // -> are there any filters with this request?
        EC2Filter[] filterSet = extractFilters(request);
        if (null != filterSet) {
            EC2VolumeFilterSet vfs = new EC2VolumeFilterSet();
            for (int i = 0; i < filterSet.length; i++)
                vfs.addFilter(filterSet[i]);
            EC2request.setFilterSet(vfs);
        }

        // -> execute the request
        EC2Engine engine = ServiceProvider.getInstance().getEC2Engine();
        DescribeVolumesResponse EC2response =
            EC2SoapServiceImpl.toDescribeVolumesResponse(ServiceProvider.getInstance().getEC2Engine().describeVolumes(EC2request), engine);
        serializeResponse(response, EC2response);
    }

    /**
     * Example of how the filters are defined in a REST request:
     * https://<server>/?Action=DescribeVolumes
     * &Filter.1.Name=attachment.instance-id
     * &Filter.1.Value.1=i-1a2b3c4d
     * &Filter.2.Name=attachment.delete-on-termination
     * &Filter.2.Value.1=true
     *
     * @param request
     * @return List<EC2Filter>
     */
    private EC2Filter[] extractFilters(HttpServletRequest request) {
        String filterName = null;
        String value = null;
        EC2Filter nextFilter = null;
        boolean timeFilter = false;
        int filterCount = 1;
        int valueCount = 1;

        List<EC2Filter> filterSet = new ArrayList<EC2Filter>();

        do {
            filterName = request.getParameter("Filter." + filterCount + ".Name");
            if (null != filterName) {
                nextFilter = new EC2Filter();
                nextFilter.setName(filterName);
                timeFilter = (filterName.equalsIgnoreCase("attachment.attach-time") || filterName.equalsIgnoreCase("create-time"));
                valueCount = 1;
                do {
                    value = request.getParameter("Filter." + filterCount + ".Value." + valueCount);
                    if (null != value) {
                        // -> time values are not encoded as regexes
                        if (timeFilter)
                            nextFilter.addValue(value);
                        else
                            nextFilter.addValueEncoded(value);

                        valueCount++;
                    }
                } while (null != value);

                filterSet.add(nextFilter);
                filterCount++;
            }
        } while (null != filterName);

        if (1 == filterCount)
            return null;
        else
            return filterSet.toArray(new EC2Filter[0]);
    }

    private void describeKeyPairs(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeKeyPairs ec2Request = new EC2DescribeKeyPairs();

        Enumeration<?> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String key = (String)names.nextElement();
            if (key.startsWith("KeyName")) {
                String[] value = request.getParameterValues(key);
                if (null != value && 0 < value.length)
                    ec2Request.addKeyName(value[0]);
            }
        }

        EC2Filter[] filterSet = extractFilters(request);
        if (null != filterSet) {
            EC2KeyPairFilterSet vfs = new EC2KeyPairFilterSet();
            for (EC2Filter filter : filterSet) {
                vfs.addFilter(filter);
            }
            ec2Request.setKeyFilterSet(vfs);
        }

        DescribeKeyPairsResponse EC2Response = EC2SoapServiceImpl.toDescribeKeyPairs(ServiceProvider.getInstance().getEC2Engine().describeKeyPairs(ec2Request));
        serializeResponse(response, EC2Response);
    }

    private void importKeyPair(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {

        String keyName = request.getParameter("KeyName");
        if (keyName == null) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - KeyName");
        }

        String publicKeyMaterial = request.getParameter("PublicKeyMaterial");
        if (publicKeyMaterial == null) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - PublicKeyMaterial");
        }

        if (!publicKeyMaterial.contains(" "))
            publicKeyMaterial = new String(Base64.decodeBase64(publicKeyMaterial.getBytes()));

        EC2ImportKeyPair ec2Request = new EC2ImportKeyPair();
        if (ec2Request != null) {
            ec2Request.setKeyName(request.getParameter("KeyName"));
            ec2Request.setPublicKeyMaterial(request.getParameter("PublicKeyMaterial"));
        }

        ImportKeyPairResponse EC2Response = EC2SoapServiceImpl.toImportKeyPair(ServiceProvider.getInstance().getEC2Engine().importKeyPair(ec2Request));
        serializeResponse(response, EC2Response);
    }

    private void createKeyPair(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        String keyName = request.getParameter("KeyName");
        if (keyName == null) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - KeyName");
        }

        EC2CreateKeyPair ec2Request = new EC2CreateKeyPair();
        if (ec2Request != null) {
            ec2Request.setKeyName(keyName);
        }

        CreateKeyPairResponse EC2Response = EC2SoapServiceImpl.toCreateKeyPair(ServiceProvider.getInstance().getEC2Engine().createKeyPair(ec2Request));
        serializeResponse(response, EC2Response);
    }

    private void deleteKeyPair(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        String keyName = request.getParameter("KeyName");
        if (keyName == null) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - KeyName");
        }

        EC2DeleteKeyPair ec2Request = new EC2DeleteKeyPair();
        ec2Request.setKeyName(keyName);

        DeleteKeyPairResponse EC2Response = EC2SoapServiceImpl.toDeleteKeyPair(ServiceProvider.getInstance().getEC2Engine().deleteKeyPair(ec2Request));
        serializeResponse(response, EC2Response);
    }

    private void getPasswordData(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        String instanceId = request.getParameter("InstanceId");
        if (instanceId == null) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - InstanceId");
        }

        GetPasswordDataResponse EC2Response = EC2SoapServiceImpl.toGetPasswordData(ServiceProvider.getInstance().getEC2Engine().getPasswordData(instanceId));
        serializeResponse(response, EC2Response);
    }

    private void createTags(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Tags ec2Request = createTagsRequest(request, response);
        if (ec2Request == null)
            return;
        CreateTagsResponse EC2Response = EC2SoapServiceImpl.toCreateTagsResponse(ServiceProvider.getInstance().getEC2Engine().modifyTags(ec2Request, "create"));
        serializeResponse(response, EC2Response);
    }

    private void deleteTags(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2Tags ec2Request = createTagsRequest(request, response);
        if (ec2Request == null)
            return;
        DeleteTagsResponse EC2Response = EC2SoapServiceImpl.toDeleteTagsResponse(ServiceProvider.getInstance().getEC2Engine().modifyTags(ec2Request, "delete"));
        serializeResponse(response, EC2Response);
    }

    private EC2Tags createTagsRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
        EC2Tags ec2Request = new EC2Tags();
        ArrayList<String> resourceIdList = new ArrayList<String>();
        Map<String, String> resourceTagList = new HashMap<String, String>();

        int nCount = 1;
        do {
            String[] resourceIds = request.getParameterValues("ResourceId." + nCount);
            if (resourceIds != null && resourceIds.length > 0)
                resourceIdList.add(resourceIds[0]);
            else
                break;
            nCount++;
        } while (true);
        if (resourceIdList.isEmpty()) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ResourceId");
        }
        ec2Request = EC2SoapServiceImpl.toResourceTypeAndIds(ec2Request, resourceIdList);

        nCount = 1;
        do {
            String[] tagKey = request.getParameterValues("Tag." + nCount + ".Key");
            if (tagKey != null && tagKey.length > 0) {
                String[] tagValue = request.getParameterValues("Tag." + nCount + ".Value");
                if (tagValue != null && tagValue.length > 0) {
                    resourceTagList.put(tagKey[0], tagValue[0]);
                } else
                    resourceTagList.put(tagKey[0], null);
            } else
                break;
            nCount++;
        } while (true);
        if (resourceTagList.isEmpty()) {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - ResourceTag");
        }
        ec2Request = EC2SoapServiceImpl.toResourceTag(ec2Request, resourceTagList);

        return ec2Request;
    }

    private void describeTags(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException {
        EC2DescribeTags ec2Request = new EC2DescribeTags();

        EC2Filter[] filterSet = extractFilters(request);
        if (null != filterSet) {
            EC2TagsFilterSet tfs = new EC2TagsFilterSet();
            for (int i = 0; i < filterSet.length; i++)
                tfs.addFilter(filterSet[i]);
            ec2Request.setFilterSet(tfs);
        }
        DescribeTagsResponse EC2Response = EC2SoapServiceImpl.toDescribeTagsResponse(ServiceProvider.getInstance().getEC2Engine().describeTags(ec2Request));
        serializeResponse(response, EC2Response);
    }

    /**
     * This function implements the EC2 REST authentication algorithm.   It uses the given
     * "AWSAccessKeyId" parameter to look up the Cloud.com account holder's secret key which is
     * used as input to the signature calculation.  In addition, it tests the given "Expires"
     * parameter to see if the signature has expired and if so the request fails.
     */
    private boolean authenticateRequest(HttpServletRequest request, HttpServletResponse response) throws SignatureException, IOException, InstantiationException,
        IllegalAccessException, ClassNotFoundException, SQLException, ParseException {
        String cloudSecretKey = null;
        String cloudAccessKey = null;
        String signature = null;
        String sigMethod = null;

        // [A] Basic parameters required for an authenticated rest request
        //  -> note that the Servlet engine will un-URL encode all parameters we extract via "getParameterValues()" calls
        String[] awsAccess = request.getParameterValues("AWSAccessKeyId");
        if (null != awsAccess && 0 < awsAccess.length)
            cloudAccessKey = awsAccess[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - AWSAccessKeyId");
        }

        String[] clientSig = request.getParameterValues("Signature");
        if (null != clientSig && 0 < clientSig.length)
            signature = clientSig[0];
        else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Signature");
        }

        String[] method = request.getParameterValues("SignatureMethod");
        if (null != method && 0 < method.length) {
            sigMethod = method[0];
            if (!sigMethod.equals("HmacSHA256") && !sigMethod.equals("HmacSHA1")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Unsupported SignatureMethod value: " + sigMethod + " expecting: HmacSHA256 or HmacSHA1");
            }
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - SignatureMethod");
        }

        String[] version = request.getParameterValues("Version");
        if (null != version && 0 < version.length) {
            if (!version[0].equals(wsdlVersion)) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Unsupported Version value: " + version[0] + " expecting: " + wsdlVersion);
            }
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - Version");
        }

        String[] sigVersion = request.getParameterValues("SignatureVersion");
        if (null != sigVersion && 0 < sigVersion.length) {
            if (!sigVersion[0].equals("2")) {
                throw new EC2ServiceException(ClientError.InvalidParameterValue, "Unsupported SignatureVersion value: " + sigVersion[0] + " expecting: 2");
            }
        } else {
            throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter - SignatureVersion");
        }

        // -> can have only one but not both { Expires | Timestamp } headers
        String[] expires = request.getParameterValues("Expires");
        if (null != expires && 0 < expires.length) {
            // -> contains the date and time at which the signature included in the request EXPIRES
            if (hasSignatureExpired(expires[0])) { //InvalidSecurity.RequestHasExpired
                throw new EC2ServiceException(ClientError.InvalidSecurity_RequestHasExpired, "Expires parameter indicates signature has expired: " + expires[0]);
            }
        } else {    // -> contains the date and time at which the request is SIGNED
            String[] time = request.getParameterValues("Timestamp");
            if (null == time || 0 == time.length) {
                throw new EC2ServiceException(ClientError.MissingParamter, "Missing required parameter -" + " Timestamp/Expires");
            }
        }

        // [B] Use the access key to get the users secret key from the cloud DB
        cloudSecretKey = userDao.getSecretKeyByAccessKey(cloudAccessKey);
        if (cloudSecretKey == null) {
            logger.debug("Access key '" + cloudAccessKey + "' not found in the the EC2 service ");
            throw new EC2ServiceException(ClientError.AuthFailure, "Access key '" + cloudAccessKey + "' not found in the the EC2 service ");
        }

        // [C] Verify the signature
        //  -> getting the query-string in this way maintains its URL encoding
        EC2RestAuth restAuth = new EC2RestAuth();
        restAuth.setHostHeader(request.getHeader("Host"));
        String requestUri = request.getRequestURI();

        // If forwarded from another basepath:
        String forwardedPath = (String)request.getAttribute("javax.servlet.forward.request_uri");
        if (forwardedPath != null) {
            requestUri = forwardedPath;
        }
        restAuth.setHTTPRequestURI(requestUri);

        String queryString = request.getQueryString();
        // getQueryString returns null (does it ever NOT return null for these),
        // we need to construct queryString to avoid changing the auth code...
        if (queryString == null) {
            // construct our idea of a queryString with parameters!
            Enumeration<?> params = request.getParameterNames();
            if (params != null) {
                while (params.hasMoreElements()) {
                    String paramName = (String)params.nextElement();
                    // exclude the signature string obviously. ;)
                    if (paramName.equalsIgnoreCase("Signature"))
                        continue;
                    // URLEncoder performs application/x-www-form-urlencoded-type encoding and not Percent encoding
                    // according to RFC 3986 as required by Amazon, we need to Percent-encode (URL Encode)
                    String encodedValue = URLEncoder.encode(request.getParameter(paramName), "UTF-8").replace("+", "%20").replace("*", "%2A");
                    if (queryString == null)
                        queryString = paramName + "=" + encodedValue;
                    else
                        queryString = queryString + "&" + paramName + "=" + encodedValue;
                }
            }
        }
        restAuth.setQueryString(queryString);

        if (restAuth.verifySignature(request.getMethod(), cloudSecretKey, signature, sigMethod)) {
            UserContext.current().initContext(cloudAccessKey, cloudSecretKey, cloudAccessKey, "REST request", null);
            return true;
        } else
            throw new EC2ServiceException(ClientError.SignatureDoesNotMatch, "The request signature calculated does not match the signature provided by the user.");
    }

    /**
     * We check this to reduce replay attacks.
     *
     * @param timeStamp
     * @return true - if the request is not longer valid, false otherwise
     * @throws ParseException
     */
    private boolean hasSignatureExpired(String timeStamp) {
        Calendar cal = EC2RestAuth.parseDateString(timeStamp);
        if (null == cal)
            return false;

        Date expiredTime = cal.getTime();
        Date today = new Date();   // -> gets set to time of creation
        if (0 >= expiredTime.compareTo(today)) {
            logger.debug("timestamp given: [" + timeStamp + "], now: [" + today.toString() + "]");
            return true;
        } else
            return false;
    }

    private static void endResponse(HttpServletResponse response, String content) {
        try {
            byte[] data = content.getBytes();
            response.setContentLength(data.length);
            OutputStream os = response.getOutputStream();
            os.write(data);
            os.close();

        } catch (Throwable e) {
            logger.error("Unexpected exception " + e.getMessage(), e);
        }
    }

    private void logRequest(HttpServletRequest request) {
        if (logger.isInfoEnabled()) {
            logger.info("EC2 Request method: " + request.getMethod());
            logger.info("Request contextPath: " + request.getContextPath());
            logger.info("Request pathInfo: " + request.getPathInfo());
            logger.info("Request pathTranslated: " + request.getPathTranslated());
            logger.info("Request queryString: " + request.getQueryString());
            logger.info("Request requestURI: " + request.getRequestURI());
            logger.info("Request requestURL: " + request.getRequestURL());
            logger.info("Request servletPath: " + request.getServletPath());
            Enumeration<?> headers = request.getHeaderNames();
            if (headers != null) {
                while (headers.hasMoreElements()) {
                    Object headerName = headers.nextElement();
                    logger.info("Request header " + headerName + ":" + request.getHeader((String)headerName));
                }
            }

            Enumeration<?> params = request.getParameterNames();
            if (params != null) {
                while (params.hasMoreElements()) {
                    Object paramName = params.nextElement();
                    logger.info("Request parameter " + paramName + ":" + request.getParameter((String)paramName));
                }
            }
        }
    }

    /**
     * Send out an error response according to Amazon convention.
     */
    private void faultResponse(HttpServletResponse response, String errorCode, String errorMessage) {
        try {
            OutputStreamWriter out = new OutputStreamWriter(response.getOutputStream());
            response.setContentType("text/xml; charset=UTF-8");
            out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            out.write("<Response><Errors><Error><Code>");
            out.write(errorCode);
            out.write("</Code><Message>");
            out.write(errorMessage);
            out.write("</Message></Error></Errors><RequestID>");
            out.write(UUID.randomUUID().toString());
            out.write("</RequestID></Response>");
            out.flush();
            out.close();
        } catch (IOException e) {
            logger.error("Unexpected exception " + e.getMessage(), e);
        }
    }

    /**
     * Serialize Axis beans to XML output.
     */
    private void serializeResponse(HttpServletResponse response, ADBBean EC2Response) throws ADBException, XMLStreamException, IOException {
        OutputStream os = response.getOutputStream();
        response.setStatus(200);
        response.setContentType("text/xml");
        XMLStreamWriter xmlWriter = xmlOutFactory.createXMLStreamWriter(os);
        xmlWriter.writeStartDocument("UTF-8", "1.0");
        MTOMAwareXMLSerializer MTOMWriter = new MTOMAwareXMLSerializer(xmlWriter);
        MTOMWriter.setDefaultNamespace("http://ec2.amazonaws.com/doc/" + wsdlVersion + "/");
        EC2Response.serialize(null, factory, MTOMWriter);
        xmlWriter.flush();
        xmlWriter.close();
        os.close();
    }
}
TOP

Related Classes of com.cloud.bridge.service.EC2RestServlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.