Package com.force.sdk.jpa.schema

Source Code of com.force.sdk.jpa.schema.ForceSchemaWriter

/**
* Copyright (c) 2011, salesforce.com, inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
*    Redistributions of source code must retain the above copyright notice, this list of conditions and the
*    following disclaimer.
*
*    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*    the following disclaimer in the documentation and/or other materials provided with the distribution.
*
*    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
*    promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

package com.force.sdk.jpa.schema;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.force.sdk.jpa.ForceManagedConnection;
import com.force.sdk.jpa.ForceStoreManager;
import com.force.sdk.jpa.table.ForceMetaData;
import com.sforce.soap.metadata.AsyncResult;
import com.sforce.soap.metadata.CustomField;
import com.sforce.soap.metadata.CustomObject;
import com.sforce.soap.metadata.DeployMessage;
import com.sforce.soap.metadata.DeployOptions;
import com.sforce.soap.metadata.DeployResult;
import com.sforce.soap.metadata.MetadataConnection;
import com.sforce.soap.metadata.PackageTypeMembers;
import com.sforce.ws.bind.TypeMapper;
import com.sforce.ws.parser.XmlOutputStream;

/**
*
* Writes schema to the Force.com database using the Metadata API. It can be used
* to create or delete objects/fields
*
* @author Fiaz Hossain
*/
public class ForceSchemaWriter extends ForceAsyncResultProcessor {

    private static final String DEPLOY_DIR = "target" + File.separator + "deploy";
    private static final String DEPLOY_ZIP_DIR = "target" + File.separator + "deploy" + File.separator + "zip";
    private static final String DELETE_MANIFEST_FILE = "destructiveChanges.xml";
    private static final String CREATE_MANIFEST_FILE = "package.xml";
    private static final String DEPLOY_ZIP = "deploy.zip";
    private static final String CUSTOM_OBJECT = "CustomObject";
    private static final String CUSTOM_FIELD = "CustomField";
    private static final String PACKAGE_VERSION = "21.0";
   
    private final SchemaDeleteProperty deleteProperty;
    private final Map<CustomObject, PackageTypeMembers> objects = new HashMap<CustomObject, PackageTypeMembers>();
    private final Map<CustomField, PackageTypeMembers> fields = new HashMap<CustomField, PackageTypeMembers>();
   
    /**
     * Creates a schema writer for generating the deploy zip and using the Metadata API
     * to manipulate schema.
     *
     * @param deleteProperty whether this application has been started with a persistence.xml
     *                       property that causes schema to be deleted rather than upserted,
     *                       may also have purgeOnDeleteSchema set
     */
    public ForceSchemaWriter(SchemaDeleteProperty deleteProperty) {
        this.deleteProperty = deleteProperty;
    }

    /**
     * Adds a custom object to the map of objects that will be created or deleted.
     *
     * @param object  the {@link CustomObject} to write to the deploy file
     * @param cmd  the metadata of the JPA entity the CustomObject represents
     * @param storeManager the store manager for entities
     * @param fmd  the Force.com specific metadata for this object
     */
    public void addCustomObject(CustomObject object, AbstractClassMetaData cmd, ForceStoreManager storeManager,
            ForceMetaData fmd) {
        //if we're deleting, include in package file if table is not read only.
        //if we're upserting, include table only if the table does not yet exist.
        addCustomObject(object, deleteProperty.getDeleteSchema()
                        ? !fmd.getIsReadOnlyTable() : !fmd.getTableImpl().getTableAlreadyExistsInOrg());

    }

    /**
     * This method is used by test cleanup. Use {@link #addCustomObject(com.sforce.soap.metadata.CustomObject,
     * org.datanucleus.metadata.AbstractClassMetaData, com.force.sdk.jpa.ForceStoreManager,
     * com.force.sdk.jpa.table.ForceMetaData)} if you're intending to use schemaCreation. includeInPackageFile
     * can be {@code true} or {@code false} depending on the table metadata.
     *
     * @param object  the object to be included in schema creation or deletion
     * @param includeInPackageFile whether the package.xml or destructiveChanges.xml file should include this object
     */
    public void addCustomObject(CustomObject object, boolean includeInPackageFile) {
        PackageTypeMembers type = null;
        if (includeInPackageFile) {
            type = new PackageTypeMembers();
            type.setName(CUSTOM_OBJECT);
            type.setMembers(new String[] {object.getFullName()});
        }
        objects.put(object, type);
    }
   
    /**
     * Adds a custom field to the map which will be written to the package file later
     * for deployment.
     *
     * @param object  the custom object this field belongs to
     * @param field  the {@link CustomField} object that will be upserted or deleted
     */
    public void addCustomField(CustomObject object, CustomField field) {
        //if we are deleting the object and it is included in the package i.e. objects contains a non null PackageTypeMember,
        // then there is no point adding fields for delete individually
        if (deleteProperty.getDeleteSchema() && objects.get(object) != null) return;
        PackageTypeMembers type = new PackageTypeMembers();
        type.setName(CUSTOM_FIELD);
        type.setMembers(new String[] {String.format("%s.%s", object.getFullName(), field.getFullName())});
        fields.put(field, type);
    }
   
    /**
     * Creates the proper deploy zip and makes the Metadata API call.
     *
     * @param mconn the Metadata API connection
     * @throws Exception thrown if something goes wrong during the schema write
     */
    public void write(ForceManagedConnection mconn) throws Exception {
        File deployDir = new File(DEPLOY_DIR);
        if (!deployDir.exists() && !deployDir.mkdirs()) {
            throw new NucleusUserException("Cannot create deploy staging directory: " + deployDir);
        }
        File deployZipDir = new File(DEPLOY_ZIP_DIR);
        if (!deployZipDir.exists() && !deployZipDir.mkdirs()) {
            throw new NucleusUserException("Cannot create directory for deploy zip: " + deployZipDir);
        }
        if (objects.size() == 0 && fields.size() == 0) return; //nothing to deploy
        List<File> schemaFiles = new ArrayList<File>();
        if (deleteProperty.getDeleteSchema()) {
            createPackageFile(DELETE_MANIFEST_FILE, objects.values(), fields.values());
            createPackageFile(CREATE_MANIFEST_FILE, null, null);
            schemaFiles.add(new File(DEPLOY_DIR, DELETE_MANIFEST_FILE));
            schemaFiles.add(new File(DEPLOY_DIR, CREATE_MANIFEST_FILE));
        } else {
            createPackageFile(CREATE_MANIFEST_FILE, objects.values(), fields.values());
            schemaFiles.add(new File(DEPLOY_DIR, CREATE_MANIFEST_FILE));
            createSchemaFiles(schemaFiles);
        }
       
        DeployOptions deployOptions = new DeployOptions();
        deployOptions.setPerformRetrieve(false);
        deployOptions.setRollbackOnError(true);
        deployOptions.setSinglePackage(true);
        deployOptions.setAutoUpdatePackage(true);
        deployOptions.setAllowMissingFiles(true);
        deployOptions.setPurgeOnDelete(deleteProperty.getPurgeSchemaOnDelete());

        MetadataConnection metadatabinding = mconn.getMetadataConnection();
        createZipFile(schemaFiles.toArray(new File[schemaFiles.size()]), DEPLOY_ZIP);
        AsyncResult asyncResult = metadatabinding.deploy(readZipFile(DEPLOY_ZIP), deployOptions);

        // Wait for the deploy to complete 
        waitForAsyncResult(metadatabinding, new AsyncResult[] {asyncResult}, true, DEPLOY_ZIP);

        DeployResult result = metadatabinding.checkDeployStatus(asyncResult.getId());
       
        // Log any messages 
        StringBuilder buf = new StringBuilder();
        if (result.getMessages() != null) {
            for (DeployMessage rm : result.getMessages()) {
                if (rm.getProblem() != null) {
                    buf.append("Error deploying: " + rm.getFileName() + " - " + rm.getProblem() + " \n ");
                }
            }
        }
        if (buf.length() > 0) {
            LOGGER.error("Deploy warnings:\n" + buf);
            throw new NucleusDataStoreException(buf.toString());
        }
    }
   
    private void createSchemaFiles(List<File> schemaFiles) throws IOException {
        TypeMapper typeMapper = new TypeMapper();
        for (CustomObject obj : objects.keySet()) {
            File f = new File(DEPLOY_DIR, obj.getFullName() + ".object");
            if (!f.exists()) {
                f.createNewFile();
            }
            FileOutputStream fos = new FileOutputStream(f);
            XmlOutputStream xout = new XmlOutputStream(fos, true);
            xout.setPrefix("", "http://soap.sforce.com/2006/04/metadata");
            try {
                xout.startDocument();
                obj.write(new QName("http://soap.sforce.com/2006/04/metadata", "CustomObject"), xout, typeMapper);
            } finally {
                xout.endDocument();
                xout.close();
                fos.close();
            }
           
            schemaFiles.add(f);
        }
    }
   
    private void createZipFile(File[] files, String zipFileName) throws IOException {
        File resultsFile = new File(DEPLOY_ZIP_DIR, zipFileName);
        ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(resultsFile)));
        byte[] buf = new byte[8092];
        try {
            for (File file : files) {
                String filename = file.getName();
                if (file.getName().endsWith(".object")) {
                    filename = "objects/" + filename;
                }
               
                LOGGER.debug("Adding file to zip: " + file.getAbsolutePath());
                // Add ZIP entry to output stream.
                zos.putNextEntry(new ZipEntry(filename));

                BufferedInputStream src = new BufferedInputStream(new FileInputStream(file));
                try {
                    int len;
                    while ((len = src.read(buf)) > 0) {
                        zos.write(buf, 0, len);
                    }
                } finally {
                    src.close();
                }
                // Close the entry
                zos.closeEntry();
            }
           
            LOGGER.debug("Results written to " + resultsFile.getAbsolutePath());
        } finally {
            zos.close();
        }
    }
   
    /**
     * Reads the zip file contents into a byte array.
     * @return byte[]
     * @throws Exception - if cannot find the zip file to deploy
     */ 
    private byte[] readZipFile(String fileName) throws Exception {
        // We assume here that you have a deploy.zip file. 
        File deployZip = new File(DEPLOY_ZIP_DIR, fileName);
        if (!deployZip.exists() || !deployZip.isFile())
            throw new Exception("Cannot find the zip file to deploy. Looking for "
                    + deployZip.getAbsolutePath());
       
        ByteArrayOutputStream bos = null;
        InputStream is = new BufferedInputStream(new FileInputStream(deployZip));
        try {
            bos = new ByteArrayOutputStream();
            int readbyte = -1;
            while ((readbyte = is.read()) != -1)  {
                bos.write(readbyte);
            }
        } finally {
            try {
                is.close();
            } finally {
                bos.close();
            }
        }
        return bos.toByteArray();
    }

    private static void createPackageFile(String fileName, Collection<PackageTypeMembers> objects,
            Collection<PackageTypeMembers> fields) throws ParserConfigurationException, TransformerException, IOException {
        /*
        <?xml version="1.0" encoding="UTF-8"?>
        <Package xmlns="http://soap.sforce.com/2006/04/metadata">
            <types>
                <members>MyCustomObject__c</members>
                <name>CustomObject</name>
            </types>
            <types>
                <members>*</members>
                <name>CustomTab</name>
            </types>
            <types>
                <members>Standard</members>
                <name>Profile</name>
            </types>
            <version>21.0</version>
        </Package>
         */
        DocumentBuilder db =
            DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document d = db.newDocument();
        Element p = d.createElementNS("http://soap.sforce.com/2006/04/metadata", "Package");
        Element version = d.createElement("version");
        version.appendChild(d.createTextNode(PACKAGE_VERSION));
        p.appendChild(version);
        if (fields != null && fields.size() > 0) {
            Node t = d.createElement("types");
            for (PackageTypeMembers type : fields) {
                Element members = d.createElement("members");
                members.appendChild(d.createTextNode(type.getMembers()[0]));
                t.appendChild(members);
            }
            Element name = d.createElement("name");
            name.appendChild(d.createTextNode(CUSTOM_FIELD));
            t.appendChild(name);
            p.appendChild(t);
        }
        if (objects != null && objects.size() > 0) {
            Node t = d.createElement("types");
            for (PackageTypeMembers type : objects) {
                if (type != null) {
                    Element members = d.createElement("members");
                    members.appendChild(d.createTextNode(type.getMembers()[0]));
                    t.appendChild(members);
                }
            }
            Element name = d.createElement("name");
            name.appendChild(d.createTextNode(CUSTOM_OBJECT));
            t.appendChild(name);
            p.appendChild(t);
        }
        d.appendChild(p);
       
        // Now write to file
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer tx = tf.newTransformer();
        DOMSource source = new DOMSource(d);
        StreamResult result = new StreamResult(new File(DEPLOY_DIR, fileName));
        tx.setOutputProperty(OutputKeys.INDENT, "yes");
        tx.transform(source, result);
    }
}
TOP

Related Classes of com.force.sdk.jpa.schema.ForceSchemaWriter

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.