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.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.EC2ImageFilterSet;
import com.cloud.bridge.service.core.ec2.EC2ImageAttributes.ImageAttribute;
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.Transaction;
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();
            try {
                EC2Prop.load( new FileInputStream( propertiesFile ));
            } 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);
            }
            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.