/* * Copyright 2010 Cloud.com, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cloud.bridge.service.controller.s3; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Calendar; 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 javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.axiom.om.OMAbstractFactory; import org.apache.axiom.om.OMFactory; import org.apache.axis2.databinding.utils.writer.MTOMAwareXMLSerializer; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.Node; import com.amazon.s3.GetBucketAccessControlPolicyResponse; import com.amazon.s3.ListAllMyBucketsResponse; import com.amazon.s3.ListBucketResponse; import com.cloud.bridge.model.SAcl; import com.cloud.bridge.model.SBucket; import com.cloud.bridge.persist.dao.SBucketDao; import com.cloud.bridge.service.S3Constants; import com.cloud.bridge.service.S3RestServlet; import com.cloud.bridge.service.S3SoapServiceImpl; import com.cloud.bridge.service.ServiceProvider; import com.cloud.bridge.service.ServletAction; import com.cloud.bridge.service.UserContext; import com.cloud.bridge.service.core.s3.S3AccessControlPolicy; import com.cloud.bridge.service.core.s3.S3CreateBucketConfiguration; import com.cloud.bridge.service.core.s3.S3CreateBucketRequest; import com.cloud.bridge.service.core.s3.S3CreateBucketResponse; import com.cloud.bridge.service.core.s3.S3DeleteBucketRequest; import com.cloud.bridge.service.core.s3.S3GetBucketAccessControlPolicyRequest; import com.cloud.bridge.service.core.s3.S3ListAllMyBucketsRequest; import com.cloud.bridge.service.core.s3.S3ListAllMyBucketsResponse; import com.cloud.bridge.service.core.s3.S3ListBucketRequest; import com.cloud.bridge.service.core.s3.S3ListBucketResponse; import com.cloud.bridge.service.core.s3.S3PutObjectRequest; import com.cloud.bridge.service.core.s3.S3Response; import com.cloud.bridge.service.core.s3.S3SetBucketAccessControlPolicyRequest; import com.cloud.bridge.service.exception.InvalidRequestContentException; import com.cloud.bridge.service.exception.NetworkIOException; import com.cloud.bridge.service.exception.PermissionDeniedException; import com.cloud.bridge.service.exception.UnsupportedException; import com.cloud.bridge.util.Converter; import com.cloud.bridge.util.StringHelper; import com.cloud.bridge.util.XSerializer; import com.cloud.bridge.util.XSerializerXmlAdapter; /** * @author Kelven Yang */ public class S3BucketAction implements ServletAction { protected final static Logger logger = Logger.getLogger(S3BucketAction.class); private DocumentBuilderFactory dbf = null; private OMFactory factory = OMAbstractFactory.getOMFactory(); private XMLOutputFactory xmlOutFactory = XMLOutputFactory.newInstance(); public S3BucketAction() { dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware( true ); } public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException, XMLStreamException { String method = request.getMethod(); if (method.equalsIgnoreCase("PUT")) { String queryString = request.getQueryString(); if (queryString != null && queryString.length() > 0) { if ( queryString.equalsIgnoreCase("acl")) { executePutBucketAcl(request, response); return; } else if(queryString.equalsIgnoreCase("versioning")) { executePutBucketVersioning(request, response); return; } else if(queryString.equalsIgnoreCase("logging")) { executePutBucketLogging(request, response); return; } } executePutBucket(request, response); return; } else if(method.equalsIgnoreCase("GET")) { String queryString = request.getQueryString(); if (queryString != null && queryString.length() > 0) { if ( queryString.equalsIgnoreCase("acl")) { executeGetBucketAcl(request, response); return; } else if(queryString.equalsIgnoreCase("versioning")) { executeGetBucketVersioning(request, response); return; } else if(queryString.equalsIgnoreCase("logging")) { executeGetBucketLogging(request, response); return; } else if(queryString.equalsIgnoreCase("location")) { executeGetBucketLocation(request, response); return; } } String bucketAtr = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY); if ( bucketAtr.equals( "/" )) executeGetAllBuckets(request, response); else executeGetBucket(request, response); } else if (method.equalsIgnoreCase("DELETE")) { executeDeleteBucket(request, response); } else throw new IllegalArgumentException("Unsupported method in REST request"); } public void executeGetAllBuckets(HttpServletRequest request, HttpServletResponse response) throws IOException, XMLStreamException { Calendar cal = Calendar.getInstance(); cal.set( 1970, 1, 1 ); S3ListAllMyBucketsRequest engineRequest = new S3ListAllMyBucketsRequest(); engineRequest.setAccessKey(UserContext.current().getAccessKey()); engineRequest.setRequestTimestamp( cal ); engineRequest.setSignature( "" ); S3ListAllMyBucketsResponse engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest); // -> serialize using the apache's Axiom classes ListAllMyBucketsResponse allBuckets = S3SoapServiceImpl.toListAllMyBucketsResponse( engineResponse ); OutputStream os = response.getOutputStream(); response.setStatus(200); response.setContentType("text/xml; charset=UTF-8"); XMLStreamWriter xmlWriter = xmlOutFactory.createXMLStreamWriter( os ); String documentStart = new String( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ); os.write( documentStart.getBytes()); MTOMAwareXMLSerializer MTOMWriter = new MTOMAwareXMLSerializer( xmlWriter ); allBuckets.serialize( new QName( "http://s3.amazonaws.com/doc/2006-03-01/", "ListAllMyBucketsResponse", "ns1" ), factory, MTOMWriter ); xmlWriter.flush(); xmlWriter.close(); os.close(); } public void executeGetBucket(HttpServletRequest request, HttpServletResponse response) throws IOException, XMLStreamException { S3ListBucketRequest engineRequest = new S3ListBucketRequest(); engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY)); engineRequest.setDelimiter(request.getParameter("delimiter")); engineRequest.setMarker(request.getParameter("marker")); engineRequest.setPrefix(request.getParameter("prefix")); int maxKeys = Converter.toInt(request.getParameter("max-keys"), 1000); engineRequest.setMaxKeys(maxKeys); S3ListBucketResponse engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest); // -> serialize using the apache's Axiom classes ListBucketResponse oneBucket = S3SoapServiceImpl.toListBucketResponse( engineResponse ); OutputStream os = response.getOutputStream(); response.setStatus(200); response.setContentType("text/xml; charset=UTF-8"); XMLStreamWriter xmlWriter = xmlOutFactory.createXMLStreamWriter( os ); String documentStart = new String( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ); os.write( documentStart.getBytes()); MTOMAwareXMLSerializer MTOMWriter = new MTOMAwareXMLSerializer( xmlWriter ); oneBucket.serialize( new QName( "http://s3.amazonaws.com/doc/2006-03-01/", "ListBucketResponse", "ns1" ), factory, MTOMWriter ); xmlWriter.flush(); xmlWriter.close(); os.close(); } public void executeGetBucketAcl(HttpServletRequest request, HttpServletResponse response) throws IOException, XMLStreamException { S3GetBucketAccessControlPolicyRequest engineRequest = new S3GetBucketAccessControlPolicyRequest(); Calendar cal = Calendar.getInstance(); cal.set( 1970, 1, 1 ); engineRequest.setAccessKey(UserContext.current().getAccessKey()); engineRequest.setRequestTimestamp( cal ); engineRequest.setSignature( "" ); engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY)); S3AccessControlPolicy engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest); // -> serialize using the apache's Axiom classes GetBucketAccessControlPolicyResponse onePolicy = S3SoapServiceImpl.toGetBucketAccessControlPolicyResponse( engineResponse ); OutputStream os = response.getOutputStream(); response.setStatus(200); response.setContentType("text/xml; charset=UTF-8"); XMLStreamWriter xmlWriter = xmlOutFactory.createXMLStreamWriter( os ); String documentStart = new String( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ); os.write( documentStart.getBytes()); MTOMAwareXMLSerializer MTOMWriter = new MTOMAwareXMLSerializer( xmlWriter ); onePolicy.serialize( new QName( "http://s3.amazonaws.com/doc/2006-03-01/", "GetBucketAccessControlPolicyResponse", "ns1" ), factory, MTOMWriter ); xmlWriter.flush(); xmlWriter.close(); os.close(); } public void executeGetBucketVersioning(HttpServletRequest request, HttpServletResponse response) throws IOException { String bucketName = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY); String versioningStatus = null; if (null == bucketName) { logger.error( "executeGetBucketVersioning - no bucket name given" ); response.setStatus( 400 ); return; } SBucketDao bucketDao = new SBucketDao(); SBucket sbucket = bucketDao.getByName( bucketName ); if (sbucket == null) { response.setStatus( 404 ); return; } String client = UserContext.current().getCanonicalUserId(); if (!client.equals( sbucket.getOwnerCanonicalId())) throw new PermissionDeniedException( "Access Denied - only the owner can read bucket versioning" ); switch( sbucket.getVersioningStatus()) { default: case 0: versioningStatus = ""; case 1: versioningStatus = "Enabled"; case 2: versioningStatus = "Suspended"; } StringBuffer xml = new StringBuffer(); xml.append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" ); xml.append( "<VersioningConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">" ); if (0 < versioningStatus.length()) xml.append( "<Status>" ).append( versioningStatus ).append( "</Status>" ); xml.append( "</VersioningConfiguration>" ); response.setStatus(200); response.setContentType("text/xml; charset=UTF-8"); S3RestServlet.endResponse(response, xml.toString()); } public void executeGetBucketLogging(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO } public void executeGetBucketLocation(HttpServletRequest request, HttpServletResponse response) throws IOException { throw new UnsupportedException( "No concept of Region is support in this EC2 implementation." ); } public void executePutBucket(HttpServletRequest request, HttpServletResponse response) throws IOException { int contentLength = request.getContentLength(); Object objectInContent = null; if(contentLength > 0) { InputStream is = null; try { is = request.getInputStream(); String xml = StringHelper.stringFromStream(is); XSerializer serializer = new XSerializer(new XSerializerXmlAdapter()); objectInContent = serializer.serializeFrom(xml); if(objectInContent != null && !(objectInContent instanceof S3CreateBucketConfiguration)) { throw new InvalidRequestContentException("Invalid rquest content in create-bucket: " + xml); } is.close(); } catch (IOException e) { logger.error("Unable to read request data due to " + e.getMessage(), e); throw new NetworkIOException(e); } finally { if(is != null) is.close(); } } S3CreateBucketRequest engineRequest = new S3CreateBucketRequest(); engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY)); engineRequest.setConfig((S3CreateBucketConfiguration)objectInContent); S3CreateBucketResponse engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest); response.addHeader("Location", "/" + engineResponse.getBucketName()); response.setContentLength(0); response.setStatus(200); response.flushBuffer(); } public void executePutBucketAcl(HttpServletRequest request, HttpServletResponse response) throws IOException { S3PutObjectRequest putRequest = null; // -> reuse the Access Control List parsing code that was added to support DIME String bucketName = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY); try { putRequest = S3RestServlet.toEnginePutObjectRequest( request.getInputStream()); } catch( Exception e ) { throw new IOException( e.toString()); } // -> reuse the SOAP code to save the passed in ACLs S3SetBucketAccessControlPolicyRequest engineRequest = new S3SetBucketAccessControlPolicyRequest(); engineRequest.setBucketName( bucketName ); engineRequest.setAcl( putRequest.getAcl()); S3Response engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest); response.setStatus( engineResponse.getResultCode()); } public void executePutBucketVersioning(HttpServletRequest request, HttpServletResponse response) throws IOException { String bucketName = (String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY); String versioningStatus = null; Node item = null; if (null == bucketName) { logger.error( "executePutBucketVersioning - no bucket name given" ); response.setStatus( 400 ); return; } try { DocumentBuilder db = dbf.newDocumentBuilder(); Document restXML = db.parse( request.getInputStream()); NodeList match = restXML.getElementsByTagName( "Status" ); if ( 0 < match.getLength()) { item = match.item(0); versioningStatus = new String( item.getFirstChild().getNodeValue()); } else { logger.error( "executePutBucketVersioning - cannot find Status tag in XML body" ); response.setStatus( 400 ); return; } // -> does not matter what the ACLs say only the owner can turn on versioning on a bucket SBucketDao bucketDao = new SBucketDao(); SBucket sbucket = bucketDao.getByName( bucketName ); String client = UserContext.current().getCanonicalUserId(); if (!client.equals( sbucket.getOwnerCanonicalId())) throw new PermissionDeniedException( "Access Denied - only the owner can turn on versioing on a bucket" ); if (versioningStatus.equalsIgnoreCase( "Enabled" )) sbucket.setVersioningStatus( 1 ); else if (versioningStatus.equalsIgnoreCase( "Suspended")) sbucket.setVersioningStatus( 2 ); else { logger.error( "executePutBucketVersioning - unknown state: [" + versioningStatus + "]" ); response.setStatus( 400 ); return; } bucketDao.update( sbucket ); } catch( PermissionDeniedException e ) { logger.error( "executePutBucketVersioning - failed due to " + e.getMessage(), e); throw e; } catch( Exception e ) { logger.error( "executePutBucketVersioning - failed due to " + e.getMessage(), e); response.setStatus(500); return; } response.setStatus(200); } public void executePutBucketLogging(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO } public void executeDeleteBucket(HttpServletRequest request, HttpServletResponse response) throws IOException { S3DeleteBucketRequest engineRequest = new S3DeleteBucketRequest(); engineRequest.setBucketName((String)request.getAttribute(S3Constants.BUCKET_ATTR_KEY)); S3Response engineResponse = ServiceProvider.getInstance().getS3Engine().handleRequest(engineRequest); response.setStatus(engineResponse.getResultCode()); response.flushBuffer(); } }