Stat Tracker

Sunday, January 9, 2011

The Death Star: Poor Project Management in Practice

This week I pulled out my Star Wars DVD's and watched them from start to finish for the first time in a few years. Those movies were the baseline for my childhood. I remember playing Star Wars with my friends on Sunday afternoons as a child. There would always be the argument over who would play Luke and who would play Han Solo, with the unluckiest kid getting stuck as Chewbacca or Lando. We'd run around the neighborhood fighing Tie Fighters and AT-AT's. But watching these holy films as an adult is a different experience entirely.

The opening scene of 'Return of the Jedi' is a prime example. Lord Vader arrives at the second Death Star which is under construction. Apparently the Death Star construction is behind schedule, and the primary stakehold (Emperor Palpatine) is not happy. So Lord Vader flies out there to speak with the Commander in charge of the construction to get them back on schedule. The commander tells Lord Vader that the timelines are ridiculous, and that he needs more men to meet the deadlines.

I watch this scene and now, I see the whole Death Star construction as a project. Complete with milestones, tasks, assigned resources,  stakeholders and status meetings. And I can't help but see a few of the problems facing the Death Star project.

1. Bad Assumptions

I can just see someone saying "Well, the second one should be easier than the first because we have learned so many lessons". Normally, yes this would be true. But you've got quite a few differences this time around. First, you've lost  Grand Moff Tarkin, arguably the father of the Death Star, when he died in the explosion of the first Death Star. Who knows how much knowledge was lost there that could impact timelines. If you lose your subject matter expert, don't expert to gain efficiencies on the next project automatically!

Secondly, even when repeating similar projects, they each have their own differences which can manifest unique problems. And I'm just talking about software development for enterprises. Imagine building giant space stations the size of a small moon!

2. Poor Escalation of Issues

The first thing the commander says to Darth Vader is "I need more men!". Really buddy? You have had resource bandwidth issues for long enough that the primary stakeholder has noticed and has had to take action? Your lucky Lord Vader didn't force choke you on the spot. You have to escalate the issues when they first crop up, not halfway through the development process.

Waiting until your already behind on the milestones and tasks is horrible project management. And just throwing more people at a problem is not going to solve it. You need bring in the right resources, not just more people. I've seen this dozen of times. A project is behind schedule, so the PM tries to just throw more hands at the problem by utilizing a bunch of offshore resources or low cost junior developers at the problem. The result? Poor quality deliverables that need to be rewritten by a more senior resource at greater expense and more time.

3. The Production Deadline Drives the Process

The Emperor engages the rebel fleet with this half constructed Death Star on purpose. Its not even fully built yet. Its fully operational in regards to the fact that its Planetary Laser is working, and he blows up a few capital ships. But a full frontal attack by capital ships was never the weakness of the Death Star, it was small fighters attacking it that made it vulnerable.

The second Death Star had the defect fixed so that the exhaust ports would not allow a direct hit to blow it up. But its outer hull and its full complement of defenses are not finished when the Emperor traps the rebels into fighting. If the Emperor had waited until the Death Star was fully completed like the first one, then there would have been no holes in the hull to allow the ships to fly to the center and blow it up!

Deploying a half completed application into production is going to bite you in the ass everytime!

Oh Palpatine, if you had just managed the construction of the second Death Star properly, you could have won! You could have crushed the rebel scum! Oh well.

Tuesday, January 4, 2011

Visualforce Page - Attachment Upload Example

I see a lot of folks posting questions in the developer forums about uploading an attachment into Salesforce through a Visualforce Page. It is actually very easy to accomplish this.

You will want to use the apex tag <apex:inputFile> to put the FileChooser Popup on the VF Page.

Inside your controller, you simply create an empty Document object, and you bind the <apex:inputFile> tag to the Document in the controller with this code here:

<apex:inputFile value="{!attach.body}" filename="{!attach.name}"/>

With your bindings all set, you just need to create an action on the controller to retrieve the values from the insert the Attachment Object. This can be done in the upload() method in the code below.

This small custom controller and Visualforce page will allow a user to upload a file and create an attachment on the record for an ID give. Here are the screenshots to show this in action.

 Once the upload() method finishes it sends the user to the new Attachment.



Here is the full code below:

AttachmentUploadController - Apex Controller
//Simple Custom Controller to Insert an Attachment into Salesforce
public with sharing class AttachmentUploadController
{
    public String parentId {get;set;}
    public Attachment attach {get;set;}
  
    public AttachmentUploadController()
    {
        attach = new Attachment();
    }
  
    //When user clicks upload button on Visualforce Page, perform upload/insert
    //Redirect user to newly inserted document
    public ApexPages.Pagereference upload()
    {
       
        //This shows how to insert an Attachment
        attach.ParentId = parentId;
        insert attach;
       
        return new ApexPages.Standardcontroller(attach).view();  
    }
}


AttachmentUpload - Visualforce Page
<apex:page controller="AttachmentUploadController">
    <apex:form >
        <apex:outputText value="Parent Object ID: "/><apex:inputText value="{!parentId}"/><br/>
        <apex:outputText value="Input File:  "/><apex:inputFile value="{!attach.body}" filename="{!attach.name}"/><br/>
        <apex:commandButton value="Upload" action="{!upload}"/>
    </apex:form>
</apex:page>



Its really that easy. Of course you'll need to add some error handling to check for null files and stuff like that, but the basics are super easy. This is a good example of a development activity that Apex makes quick and easy.

Of course when you hit Apex limits or things it doesn't do well, thats when you want to pull your hair out.

Monday, January 3, 2011

Building Dynamic SOQL - Select All Query

Salesforce SOQL does not allow select * queries. For example, you cannot do "Select * from Account where Id = 'XXXXXXXX'". This can be confusing to new Apex developers who are familiar with SQL, since SOQL syntax is very close to SQL. This presents developers with the question: How can I dynamically query for all the fields on an object? The answer is you can use Salesforce Schema Describe objects to dynamically build SOQL queries at run time to query for all fields on a record.

In this post, we are going to use the Schema Describe methods to do a few interesting things.
  1. We are going to dynamically determine the Object Type based on the ID at run-time.
  2. We are going to retrieve all the field definitions for the Object.
  3. We are going to build a SOQL query dynamically to query for all the fields.
  4. Just for fun we are going to populate the results dynamically in a Visualforce Page.
 The first thing we need to do is use the Schema classes to retrieve the SObject describe results into memory:
     Map<String,Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();

Now that we have the Schema SObjects in memory, we can iterate over the SObject to determine which type of SObject this ID referers to by calling this code here:


        List<Schema.SObjectType> sobjects = schemaMap.values();
        List<Sobject> theObjectResults;
        Schema.DescribeSObjectResult objDescribe;
        List<Schema.SObjectField> tempFields;
        for(Schema.SObjectType objType : sobjects)
        {
            objDescribe = objType.getDescribe();
            String sobjectPrefix = objDescribe.getKeyPrefix();
            if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
            {
                objectType = objDescribe.getLocalName();
                Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
                tempFields = fieldMap.values();
                for(Schema.SObjectField sof : tempFields)
                {
                    fields.add(sof.getDescribe());
                }
                getAllQuery = buildQueryAllString(fields,objDescribe,id);
            }
        }
       
        resultObject = Database.query(getAllQuery);
       
        for(Schema.DescribeFieldResult dfr : fields)
        {
            fieldVals.add(new GenericFieldVO(dfr,resultObject));
        }

We use the objDescribe.getKeyPrefix() method to retrieve the prefix for the Object. This is a great API call that I seldom see used. Many times I see developers hard-code the prefix in the code, which I personally can't stand. After we have the prefix, we check that against the ID to see if the ID starts with the prefix. If it does, then we know that this is the Object Describe to use.

Once we have the correct SObject definition, then we can get all the field definitions by calling objDescribe.fields.getMap(). This returns a Map of the SObjectField type which contains the field definitions (labels, data type, etc). With this information we are now ready to build a dynamic query which will query for all the data for this particular ID.
    //Build the Query String
    private String buildQueryAllString(List<Schema.DescribeFieldResult> queryFields,DescribeSObjectResult obj, String theId)
    {
        String query = QUERY_SELECT;
        for(Schema.DescribeFieldResult dfr : queryFields)
        {
            query = query + dfr.getName() + ',';
        }
        query = query.subString(0,query.length() - 1);
        query = query + QUERY_FROM;
        query = query + obj.getName();
        query = query + QUERY_WHERE;
        query = query + theId + '\'';
        system.debug('Build Query == ' + query);
        return query;
    }


Bam. We now have the ability to build dynamic queries which will retrieve all the information for a object. For this example I have built a Visualforce Page which displays the dynamic values. I will included the full source for this at the bottom of this post. Here is the output of our dynamic SOQL calls for when I give it an Contact ID:
And then I just provide a second ID of a Account:




You can see that the values are dynamically queried and populated on the screen. Of course this particular example doesn't have much business value, you can always just put http://www.salesforce.com/XXXXXXXXX where XXXXXXX is your ID and go straight to the record. But this example shows that you can build dynamic SOQL queries to do 'Select *' type functionality with relative ease.

Now here is the full Apex code dump of this simple page.

To test, just simply pass a URL like https://c.na3.visual.force.com/apex/GenericSelectAll?id=XXXXXXXXX into your browser where ?id=XXXXXXXXXXX is your ID.

SchemaManager
//This class will do all the methods to retrieve schema infomraiton on SObject for apex
//This will ensure that multiple calls to descibes aren't called so we don't hit gov limits
public with sharing class SchemaManager
{
    private static Map<String, Schema.SObjectType> sobjectSchemaMap;
   
    public static Map<String,Schema.SObjectType> getSchemaMap()
    {
        if(sobjectSchemaMap == null)
        {
            sobjectSchemaMap = Schema.getGlobalDescribe();
        }
        return sobjectSchemaMap;
    }
   
    //Retrieve the specific Schema.SobjectType for a object so we can inspect it
    public static Schema.SObjectType getObjectSchema(String objectAPIName)
    {
        getSchemaMap();
        Schema.SObjectType aSObjectType = sobjectSchemaMap.get(objectAPIName);
        return aSobjectType;
    }
}


GenericSelectAllController
//A simple custom controller that will take any ID as input
//and query for all fields (up to 90) on the SObject dynamically
public with sharing class GenericSelectAllController
{
    public List<Schema.DescribeFieldResult> fields {get;set;}
    public List<GenericFieldVO> fieldVals {get;set;}
    public String getAllQuery {get;set;}
    public SObject resultObject {get;set;}
    public String objectType {get;set;}
    public String searchId {get;set;}
    public List<SObject> vals {get;set;}
    public static final String ERROR_ID_MISSING = 'There was no id passed in the parameters. Id is required.';
    public static final String QUERY_SELECT = 'select ';
    public static final String QUERY_FROM = ' from ';
    public static final String QUERY_WHERE = ' where Id = \'';
    //Instantiate the controller. If there is no ID then send the error message to the page.
    public GenericSelectAllController()
    {
        init();
        String id = ApexPages.currentPage().getParameters().get('id');
        if(id == null || id == '')
        {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL,ERROR_ID_MISSING));
        }
        else
        {
            searchId = id;
            processSchemaInfo(id);
        }
    }
   
    //Process the Schema information
    //1. Retrieve the global Schema Information
    //2. Iterate over the SObject Schema Information
    //2.A Retrieve the SObject Key Prefix and match to the ID passed into page
    //2.B Describe all the Fields for the SObject
    //2.C Build a Query String from all the Fields
   
    private void processSchemaInfo(String id)
    {
        system.debug(id);
        Map<String,Schema.SObjectType> schemaMap = SchemaManager.getSchemaMap();
        List<Schema.SObjectType> sobjects = schemaMap.values();
        List<Sobject> theObjectResults;
        Schema.DescribeSObjectResult objDescribe;
        List<Schema.SObjectField> tempFields;
        for(Schema.SObjectType objType : sobjects)
        {
            objDescribe = objType.getDescribe();
            String sobjectPrefix = objDescribe.getKeyPrefix();
            if(id != null && sobjectPrefix != null && id.startsWith(sobjectPrefix))
            {
                objectType = objDescribe.getLocalName();
                Map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();
                tempFields = fieldMap.values();
                for(Schema.SObjectField sof : tempFields)
                {
                    fields.add(sof.getDescribe());
                }
                getAllQuery = buildQueryAllString(fields,objDescribe,id);
            }
        }
       
        resultObject = Database.query(getAllQuery);
       
        for(Schema.DescribeFieldResult dfr : fields)
        {
            fieldVals.add(new GenericFieldVO(dfr,resultObject));
        }
       
    }
   
    private void init()
    {
        getAllQuery = '';
        fields = new List<Schema.DescribeFieldResult>();
        fieldVals = new List<GenericFieldVO>();
    }
   
    //Build the Query String
    private String buildQueryAllString(List<Schema.DescribeFieldResult> queryFields,DescribeSObjectResult obj, String theId)
    {
        String query = QUERY_SELECT;
        for(Schema.DescribeFieldResult dfr : queryFields)
        {
            query = query + dfr.getName() + ',';
        }
        query = query.subString(0,query.length() - 1);
        query = query + QUERY_FROM;
        query = query + obj.getName();
        query = query + QUERY_WHERE;
        query = query + theId + '\'';
        system.debug('Build Query == ' + query);
        return query;
    }
}




GenericFieldVO


GenericFieldVO
/* ============================================================
 * Part of this code has been modified from source that is part of the "apex-lang" open source project avaiable at:
 *
 *      http://code.google.com/p/apex-lang/
 *
 * This code is licensed under the Apache License, Version 2.0.  You may obtain a
 * copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 * ============================================================
 */


// A Generic View Object which is used to wrap generic SObject Field Data for Visualforce Page display.
public with sharing class GenericFieldVO
{
    public String fieldLabel {get;set;}
    public String stringVal {get;set;}
    public Boolean boolVal {get;set;}
    public Date dateVal {get;set;}
    public Integer intVal {get;set;}
    public Double doubleVal {get;set;}
    public DateTime dateTimeVal {get;set;}
    public ID idVal {get;set;}
    public Boolean isBool {get;set;}
   
    private static final List<Schema.DisplayType> STRING_TYPES      = new List<Schema.DisplayType>{
    Schema.DisplayType.base64
    ,Schema.DisplayType.Email
    ,Schema.DisplayType.MultiPicklist
    ,Schema.DisplayType.Phone
    ,Schema.DisplayType.Picklist
    ,Schema.DisplayType.String
    ,Schema.DisplayType.TextArea
    ,Schema.DisplayType.URL
    };
    private static final List<Schema.DisplayType> INTEGER_TYPES     = new List<Schema.DisplayType>{
        Schema.DisplayType.Integer
    };
    private static final List<Schema.DisplayType> ID_TYPES          = new List<Schema.DisplayType>{
        Schema.DisplayType.ID
        ,Schema.DisplayType.Reference
    };
    private static final List<Schema.DisplayType> DOUBLE_TYPES      = new List<Schema.DisplayType>{
        Schema.DisplayType.Currency
        ,Schema.DisplayType.Double
        ,Schema.DisplayType.Percent
    };
    private static final List<Schema.DisplayType> DATETIME_TYPES    = new List<Schema.DisplayType>{
        Schema.DisplayType.DateTime
    };
    private static final List<Schema.DisplayType> DATE_TYPES        = new List<Schema.DisplayType>{
        Schema.DisplayType.Date
    };
    private static final List<Schema.DisplayType> BOOLEAN_TYPES     = new List<Schema.DisplayType>{
        Schema.DisplayType.Boolean
        ,Schema.DisplayType.Combobox
    };

   
   
    public GenericFieldVO(Schema.DescribeFieldResult sourceField, SObject source)
    {
        fieldLabel = sourceField.getLabel();
        if(contains(STRING_TYPES,sourceField.getType())){
            stringVal = (String)source.get(sourceField.getName());
        } else if(contains(INTEGER_TYPES,sourceField.getType())){
            intVal = (Integer)source.get(sourceField.getName());
        } else if(contains(ID_TYPES,sourceField.getType())){
            idVal = (ID)source.get(sourceField.getName());
        } else if(contains(DOUBLE_TYPES,sourceField.getType())){
           doubleVal = (Double)source.get(sourceField.getName());
        } else if(contains(DATETIME_TYPES,sourceField.getType())){
           dateTimeVal = (DateTime)source.get(sourceField.getName());
        } else if(contains(DATE_TYPES,sourceField.getType())){
            dateVal = (Date)source.get(sourceField.getName());
        } else if(contains(BOOLEAN_TYPES,sourceField.getType())){
           boolVal = (Boolean)source.get(sourceField.getName());
           isBool = true;
        }
    }
   
    private static Boolean contains(List<Schema.DisplayType> aListActingAsSet, Schema.DisplayType typeToCheck){
        if(aListActingAsSet != null && aListActingAsSet.size() > 0){
            for(Schema.DisplayType aType : aListActingAsSet){
                if(aType == typeToCheck){
                    return true;
                }
            }
        }
        return false;
    }
}

GenericSelectAllPage
<apex:page controller="GenericSelectAllController">
    <style>
        table {width:100%;}
        td {width:100%;}
    </style>
    <apex:messages />
    <apex:pageBlock title="Generic Search All: {!searchId}">
        <apex:outputPanel layout="block" style="width:100%;" rendered="{!resultObject != null}" id="SobjectDetailPanel">
            <apex:outputText style="font-weight:bold;" value="Type: {!objectType}"/>
            <br/>
            <br/>
            <apex:repeat value="{!fieldVals}" var="fieldVal">
                <apex:outputText style="font-weight:bold;" value="{!fieldVal.fieldLabel}: "/>
                <apex:outputPanel rendered="{!fieldVal.stringVal != null}">
                    <apex:outputText value="{!fieldVal.stringVal}"/>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!fieldVal.isBool == true}">
                    <apex:outputText value="{!fieldVal.boolVal}"/>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!fieldVal.idVal != null}">
                    <a href="/{!fieldVal.idVal}"><apex:outputText value="{!fieldVal.idVal}"/></a>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!fieldVal.intVal != null}">
                    <apex:outputText value="{0, number, 0}">
                       <apex:param value="{!fieldVal.intVal}" />
                     </apex:outputText>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!fieldVal.dateVal != null}">
                    <apex:outputText value="{0,date,yyyy.MM.dd}">
                       <apex:param value="{!fieldVal.dateVal}" />
                    </apex:outputText>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!fieldVal.dateTimeVal != null}">
                    <apex:outputText value="{0,date,yyyy.MM.dd G 'at' HH:mm:ss z}">
                       <apex:param value="{!fieldVal.dateTimeVal}" />
                    </apex:outputText>
                </apex:outputPanel>
                <apex:outputPanel rendered="{!fieldVal.doubleVal != null}">
                    <apex:outputText value="{0, number,0.00}">
                       <apex:param value="{!fieldVal.doubleVal}" />
                     </apex:outputText>
                </apex:outputPanel>
                <br/>
                <br/>
            </apex:repeat>
        </apex:outputPanel>
    </apex:pageBlock>
</apex:page>