Platform Developer Certification – Transition Exam

As a developer, I wanted to learn as much as possible and a good way to do it is getting any of the certifications that Salesforce offers to us.

I was able to get 401 and 501 credentials, so I decided to share some tips about them writing an entry some months ago.

However a year ago, Salesforce decided to modify certifications and 401 and 501 were not available anymore.

So what could I do? Do I lose my credentials? No at all. I still can say that I got these certifications. However Salesforce also advices to us to move to the new one.

In order to do it we have different paths as you can see on below image:

Captura de pantalla 2016-07-12 a las 15.09.09

I decided to go for the combo Transition Exam, but lot of questions came to my mind. So let me share all answers I got. Maybe they can also help you.

Q – How is the Exam?

A – This is a test exam. We have 30 minutes to answer 16 multi-choice questions, and we need to get 63% (11 questions) right to pass the exam. Per my experience, don’t waste the time. Read carefully and if you are not completely sure, mark to review it later and move to the next one.

Q – When do I get the results?

A – After submitting the exam. As usual, we would get the Pass or Fail response. Unfortunately we don’t get the score. You can only see if you passed it or not.

Q – Is this a proctored exam?

A – Yes !! So take care and prepare your place for the exam in advance.

Q – What can I do if I fail the exam?

A – Good questions!! That also worried to me. This is a free exam and you can try to pass it as many times as you need. The only thing that you need to take into account is that you cannot try it more than 3 times per release. This mean. Salesforce deliver 3 releases a year, so you have 9 opportunities to do it per year.

Q – What should I study to pass it? 

A – There is no guide to know what you need. To be honest, you should get Platform Developer I and II guides and check what it is required for them and study the same. I reviewed all the material that I used to pass 501 exam. And in addition, I checked:

Obviously, Trailheads are also very useful to remember certain contents for example Life cycle one.

Q – Should I look for any other material?

A – Something I didn’t find on above documents and I was asked for was about:

  • Compounds fields
  • Certifications, kind of certifications depending on device …
  • Streaming API

Actually the exam was a challenged and that also helped me to learn even more.

Q – What kind of questions could I find?

A – This test exam provided different options and you need to select one or many responses.

An example could be (This is not a question of my exam. This is an example I have created based on what I found during the exam)

Having this piece of code:

@RestResource(urlMapping='/user_defined_type_example/*')
global with sharing class MyOwnTypeRestResource 
{
   @HttpPost
   global static MyUserDefinedClass echoMyType(MyUserDefinedClass ic)
   {
      return ic;
   }

   global class MyUserDefinedClass
   {
      global String string1;
      global String string2 { get; set; }
      private String privateString;
      global transient String transientString;
      global static String staticString;
   }
}

And these JSON calls:

1. 
{
   "ic" : {
             "string1" : "value for string1",
             "string2" : "value for string2",
             "privateString" : "value for privateString"
          }
}

2.
{
   "ic" : {
             "string1" : "value for string1",
             "string2" : "value for string2",
          }
}

3.
{
   "ic" : {
             "string1" : "value for string1",
             "string2" : "value for string2",
             "transientString" : "value for transientString",
             "staticString" : "value for staticString"
           }
}

Which is the correct call?
a) 1
b) 2
c) 3
d) 1 and 3
e) 1 and 2
f) 1, 2 and 3

Q – Once I pass the exam. Do I have to do anything else? Assignment or any other test?

A – No !! Since that moment you get your new credentials and you only need to do the maintenance exam as usual 3 times a year.

Q – I have Force.com Developer (401) certification. Does the maintenance exam cover also that one?

A – I’m afraid that Platform Developers maintenance don’t cover 401 certifications, so you would have to do 2 exams per release. But nothing to worry too much.

Do you have any other question? You can open a case here.

Once you get it you can also check your certification in the verification page. Something that I will miss from this page is my Advance Developer logo.

Captura de pantalla 2016-07-12 a las 15.03.17

By the way, the correct answer on above question is option e).

 

 

 

Mi primera Visualforce III – Controladores

En este post vamos a continuar desde dónde lo dejamos. En la última entrada en Español hablábamos de cómo crear una página visualforce utilizando únicamente un Controlador Standard. Si todavía no has tenido la oportunidad de leerla sería interesante que lo hicieras antes de continuar. Aquí tienes el link.

Controlador Personalizado

Hasta ahora sólo hemos usado el controlador Standard asociado al objeto que usábamos en la página, Account. Y con él hemos conseguido hacer muchas cosas. Entonces, ¿por qué necesito crear uno? Con este tipo de controladores vamos a extender la funcionalidad, de forma que nuestra página puede llegar a ser más potente si cabe.

Tenemos dos tipos de controladores personalizados:

  • Asociados a una lista de registros de un objeto
  • Asociados a un único registro de un objeto

Dejaremos la lista de registros para un blog futuro y hoy vamos a centrarnos en qué hacer con un único registro.

Único Registro

Estos controladores se usarán cuando quiero realizar una acción asociada a un registro en concreto.

Para empezar sólo tengo que crear una clase Apex

Setup | Develop | Apex Classes

Y hacer click en el botón New.

A continuación sólo tienes que escribir estas líneas de código y guardar:

public with sharing class AccountController
{
    public AccountController(ApexPages.StandardController controller){}
}

Antes de continuar ¿qué podemos destacar del código anterior?

  1. Una clase puede ser:
    1. private: la clase es sólo conocida de forma local, es decir por el código de esa clase.
    2. public: la clase es visible en toda la aplicación.
    3. global: la clase es visible por cualquier código Apex en cualquier sitio, es decir, si empaquetamos la clase, ésta será visible y accesible para cualquier usuario que haya instalado el paquete. Mi recomendación por ahora es que tu clase sea pública de forma que así también puedes proteger la propiedad intelectual de tu clase si decides comercializarla en un futuro.
  2. With Sharing nos permite indicar permisos de acceso. Échale un vistazo a este blog en inglés de Andy Fawcett dónde explica el significado de estás palabras
  3. Palabra Controller en el nombre de la clase. Es una opción personal para saber que esa clase es un Controlador, pero si tu prefieres darle otro nombre, ¡adelante! sólo una recomendación. Mantén una misma convención en cuanto a nombres se refiere a lo largo de todo tu desarrollo, así que si creas otro controlador, dale un nombre similar
  4. Un constructor con un único argumento del tipo ApexPages.StandardController que proporciona una serie de métodos para poder recoger información del registro asociado a ese controlador

Una vez que tenemos el controller creado, vamos a asociarlo a una página. Por ejemplo, vamos a extender la funcionalidad de la página accountedit que creamos en el post anterior. Para ello sólo tenemos que añadir el atributo extensions a la definición de la página.

<apex:page standardController="Account" extensions="AccountController">
</apex:page> 

Y ahora, ¿tenemos que hacer algún otro cambio? No en principio podemos mantener el mismo código en la página aunque lo asociemos a un controlador custom.


<!-- accountedit -->
<apex:page standardController="Account" extensions="AccountController">
  <apex:form >
    <apex:pageBlock title="Vista Cuenta">
      <apex:pageBlockButtons >
        <apex:commandButton action="{!save}" value="Save"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Campos Cuenta">
        <apex:inputField value="{!account.name}"/>
        <apex:inputField value="{!account.site}"/>
        <apex:inputField value="{!account.type}"/>
        <apex:inputField value="{!account.accountNumber}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Como ya aprendimos en el post anterior, si sobre escribimos el botón Edit del objeto Cuenta para mostrar nuestra página, el resultado será el siguiente:

Captura de pantalla 2016-01-18 a las 16.31.57

Entonces, ¿cual es la diferencia?

Por ahora ninguna.

Pero vamos a seguir añadiendo funcionalidad. En la página view del anterior post, mostrábamos además una lista relacionada con las Oportunidades asociadas a la cuenta seleccionada. Vamos a hacer lo mismo en nuestra página pero esta vez vamos a usar el tag  apex:BlockTable.  Este tag tiene como atributo value que usaremos para llamar al método que nos devuelve la lista de registros sobre la que vamos a iterar en la tabla. Y el atributo var que será la variable que usaremos en cada iteración.

<apex:pageBlockTable title="Opportunities" value="{!opportunityList}" var="opp">
</apex:pageBlockTable>

¿Y qué es opportunityList? Ahí es cuando entra en acción nuestro nuevo controlador dónde tendremos un método que via una consulta a base de datos (en Salesforce se llaman SOQL) obtendremos la lista de Oportunidades asociadas a la cuenta Account1.

Una vez que tenga esta información, usaremos los métodos get / set que al final serán los que el atributo value llamará.


public with sharing class AccountController
{
    private Id m_accountId;
    private List<Opportunity> m_opportunities;
    private ApexPages.StandardController m_controller;

    public AccountController(ApexPages.StandardController controller)
    {
        m_controller = controller;
        m_accountId = m_controller.getRecord().Id;
        m_opportunities = calculateOpportunities(m_accountId);
    }
   
    public List<Opportunity> getOpportunityList()
    {
        return m_opportunities;
    }

    public void setOpportunityList(List<Opportunity> value)
    {
        m_opportunities = value;
    }

    private List<Opportunity> calculateOpportunities(Id accId)
    {
        //Remember Separation of Concerns
        //Move to a Selector class
        List<Opportunity> oppList = [Select Id, Name From Opportunity Where AccountId = :accId];

        return oppList;
    }
}

Cómo hemos mencionado antes del código, tenemos una variable privada m_opportunities que será utilizada por el get / set. Y si te fijas, estos métodos tienen el nombre que hemos usado en nuestra página visualforce.

Además, tenemos un método calculateOpportunities donde vamos a hacer la consulta a base de datos, la SOQL. Recuerda que aunque en este ejemplo estoy haciendo una SOQL en un controlador, debemos mantener el SOC. Para más información echa un vistazo aquí.

Y este método será llamado en el constructor del controlador y lo usaremos para modificar la variable privada m_opportunities de forma que una vez que se llame al controlador, la lista de Oportunidades se rellenará automáticamente.

Otra cosa a destacar, es el filtro de nuestra consulta. Buscamos las Oportunidades cuyo campo Account tenga el mismo valor que la cuenta que tengo en mi página. Si recuerdas, ya hemos comentado que StandardController ofrece muchos métodos, entre ellos getRecord() que nos devolverá el registro que estamos visualizando en el momento de llamar a nuestro controlador.

El último paso antes de visualizar el resultado, es añadir alguna columna a nuestra tabla. Por ahora sólo añadiremos el Nombre de la oportunidad.


<apex:pageBlock title="Related Lists">
  <apex:pageBlockTable title="Opportunities" value="{!opportunityList}" var="opp" >
    <apex:column headerValue="Name">
      <apex:outputText value="{!opp.Name}"/>
    </apex:column>
  </apex:pageBlockTable>
</apex:pageBlock>

Sabiendo todo esto, ¿cual es el resultado?

Captura de pantalla 2016-01-21 a las 19.51.57.png

¿Pero y si queremos añadir un campo que no está en el objeto Oportunidad y mostrarlo en la tabla? Por ejemplo, me gustaría visualizar un checkbox que me indicara si la Oportunidad tiene estado Closed Won.

Al ser un campo calculado, la lista que devolveremos no será directamente del tipo Oportunidad. Lo que vamos a hacer es crearnos una clase interna con dos variables, por un lado el registro de la oportunidad, y por otro este nuevo campo que vamos a calcular. Una vez que lo tengamos, la lista que usaremos en la página será del tipo de la clase interna.

Por otro lado, en el método que calcula las Oportunidades asociadas a la Cuenta, haremos los cálculos pertinentes para rellenar el nuevo campo que crearemos.

Así quedaría el controlador:


public with sharing class AccountController
{
    private Id m_accountId;
    private List<OpportunityInfo> m_opportunities;
    private ApexPages.StandardController m_controller;

    public AccountController(ApexPages.StandardController controller)
    {
        m_controller = controller;
        m_accountId = controller.getRecord().Id;
        m_opportunities = calculateOpportunities(m_accountId);
    }

    public List<OpportunityInfo> getOpportunityList()
    {
        return m_opportunities;
    }

    public void setOpportunityList(List<OpportunityInfo> value)
    {
        m_opportunities = value;
    }

    private List<OpportunityInfo> calculateOpportunities(Id accId)
    {
        Boolean isClosed;
        OpportunityInfo oppInfo;
        List<OpportunityInfo> oppInfoList = new List<OpportunityInfo>();

        //Remember Separation of Concerns
        //Move to a Selector class
        for(Opportunity opp : [Select Id, Name,
                                 StageName, Amount,
                                 CloseDate
                               From Opportunity
                               Where AccountId = :accId])
        {
            isClosed = opp.StageName == 'Closed Won' ? true : false;
            oppInfo = new OpportunityInfo(opp, isClosed);
            oppInfoList.add(oppInfo);
        }
        return oppInfoList;
     }

     //Inner class
     private class OpportunityInfo
     {
        public Opportunity OpportunityRecord {get; private set;}
        public Boolean ToClose {get; set;}

        private OpportunityInfo(Opportunity oppValue, Boolean toCloseValue)
        {
            OpportunityRecord = oppValue;
            ToClose = toCloseValue;
        }
      }
}

Y en la página visualforce, la tabla tendrá que hacer uso de la clase interna


<apex:pageBlock title="Related Lists">
   <apex:pageBlockTable title="Opportunities" value="{!opportunityList}" var="opp" >
      <apex:column headerValue="Name">
         <apex:outputField value="{!opp.OpportunityRecord.Name}"/>
      </apex:column>
      <apex:column headerValue="is Closed?">
         <apex:inputCheckbox value="{!opp.ToClose}" disabled="{!opp.ToClose}"/>
      </apex:column>
      <apex:column headerValue="Stage">
         <apex:outputField value="{!opp.OpportunityRecord.StageName}"/>
      </apex:column>
      <apex:column headerValue="Amount">
         <apex:outputField value="{!opp.OpportunityRecord.Amount}"/>
      </apex:column>
      <apex:column headerValue="CloseDate">
         <apex:outputField value="{!opp.OpportunityRecord.CloseDate}"/>
      </apex:column>
   </apex:pageBlockTable>
</apex:pageBlock>

Siendo el resultado:

Captura de pantalla 2016-01-21 a las 20.11.08.png

En este ejemplo todos los campos de la tabla son de sólo lectura. El único que es input es el campo personalizado que hemos creado a partir de la clase interna. Pero si te fijas, en todos los registros el checkbox es de tipo lectura menos en el último en que el campo es de escritura porque el Stage no es Closed Won. Sin embargo el tag que hemos usado en todos los casos es apex:inputCheckbox. ¿Por qué no usamos output? Porque este tag sólo da la opción de escritura. Sin embargo ofrece un atributo para deshabilitarlo y es el que hemos usado, disabled.

Por último, nos podríamos preguntar por el botón Save. Si mantenemos en nuestra página visualforce la misma línea para el botón save que en el post anterior, la funcionalidad será la misma. Usará el controlador Standard para hacer la modificación en base de datos.

Sin embargo podemos modificar esa funcionalidad para extenderla. Para ello tendremos que crear un método en nuestro controlador dónde añadiremos la funcionalidad que necesitemos y devolveremos la página hacia donde quiero navegar después de la modificación. En nuestro caso la versión sólo lectura de nuestra cuenta.

public PageReference saveUpdates()
{
    //TODO: Acciones para salvar
    return new PageReference('/' + m_accountId);
} 

Y hacer una llamada a ese método en nuestra página.

<apex:pageBlockButtons >
    <apex:commandButton action="{!saveUpdates}" value="Save"/>
</apex:pageBlockButtons> 

Como resumen, en esta entrada hemos aprendido:

  • Cómo crear un controlador asociado a un único registro
  • Cómo asociarlo a una visualforce
  • Cómo mostrar campos en nuestra página visualforce
  • Cómo crear un tabla con valores que vienen de base de datos
  • Cómo añadir a la tabla un campo que no existe en base de datos
  • Cómo modificar el botón Standard Save

Nos vemos en el próximo post.

 

Lightning Connect and Apex Connector Framework

After publishing 2 entries talking about

On this one I would like to go one step further on Lightning Connect feature – Apex Connector Framework

As you know, Lightning Connect would allow us to link an external repository to our Salesforce organization. In order to do this connection we can use an existing OData provider or create something by ourselves like we can read on the first Lightning Connect post.

However, since Summer’15 we have the new Apex Connector Framework General Available, so everybody can use it and get advantage of it. At the end we are going to create the connection all in apex so we can forget any other language.

Checa Hotel – Use Case

Let’s back to the same example that we have talked about till now, Checa Hotel one and its reservations.

In order to work with Apex Connector we need to create 2 classes:

HotelReservationConnection

This class must extend DataSource.Connection and although Salesforce documentation shows it as global, you could check on this example that it works properly if we make it public. My advice is to create a global class if we want to expose the code outside of our package, otherwise, it could not be desirable.

public with sharing class HotelReservationConnection extends DataSource.Connection
{
    private DataSource.ConnectionParams connectionInfo;
    public HotelReservationConnection(DataSource.ConnectionParams connectionParams)
    {
        this.connectionInfo = connectionParams;
    }
}

Now we need to override 3 methods:

Sync

This method will create the new object and its fields. At the end when we click on Sync And Validate button during the creation of the External Data Source, this method would be called.


override public List<DataSource.Table> sync()
{
    List<DataSource.Table> tables = new List<DataSource.Table>();
    List<DataSource.Column> columns;

    columns = new List<DataSource.Column>();

    //Standard fields
    columns.add(DataSource.Column.url('DisplayUrl'));
    columns.add(DataSource.Column.text('ExternalId',255));

    //Custom fields
    columns.add(DataSource.Column.get('Name', 'Name', 'Name Field for the table', true, true, DataSource.DataType.STRING_SHORT_TYPE, 10, 0));
    columns.add(DataSource.Column.get('EndDate', 'End Date', '',true, true, DataSource.DataType.STRING_SHORT_TYPE, 10, 0));
    columns.add(DataSource.Column.get('GuestName', 'Guest Name', 'Name of the Guest', true, true, DataSource.DataType.STRING_LONG_TYPE, 80, 0));
    columns.add(DataSource.Column.get('Paid', 'Paid', '',true, true, DataSource.DataType.BOOLEAN_TYPE, 255, 0));
    columns.add(DataSource.Column.get('Price', 'Price', '',true, true, DataSource.DataType.NUMBER_TYPE, 18, 2));
    columns.add(DataSource.Column.get('RoomNumber', 'Room Number', '',true, true, DataSource.DataType.NUMBER_TYPE, 18, 0));
    columns.add(DataSource.Column.get('SpecialRequirement', 'Special Requirement', '',true, true, DataSource.DataType.STRING_LONG_TYPE, 255, 0));
    columns.add(DataSource.Column.get('StartDate', 'Start Date', '',true, true, DataSource.DataType.STRING_SHORT_TYPE, 10, 0));

    //Table creation
    DataSource.Table newTable = new DataSource.Table();
    newTable.labelSingular = 'New Hotel Reservation';
    newTable.labelPlural = 'New Hotel Reservations';
    newTable.name = 'NewHotelReservation';
    newTable.description = 'Hotel Reservations via Apex Connector';
    newTable.nameColumn = 'Name';
    newTable.columns = columns;

    tables.add(newTable);

    return tables;
}

We can highlight 3 blocks

  • Standard Fields: We only have to define 2 columns and specify their API Names. These 2 rows are required:
    • Display ULR that at end is a link.
    • External Id that will help us to set the uniqueness across the whole organization.
  • Custom Fields: At this point we are going to specify all fields that we want to show in our object. There are 2 ways to specify every single column:

columns.add(DataSource.Column.text(‘GuestName’,255));

We only specify the API name and its Length.

columns.add(DataSource.Column.get(‘GuestName’,
‘Guest Name’,
‘Name of the Guest’,
true, true,
DataSource.DataType.STRING_LONG_TYPE,
80, 0));

In the case we want to add more information the the field. On above line we can see the API Name, Label, Description, Filtering Disabled field, Sorting Disabled field, Data type and Length.

  • Table creation: We will specify some features of the new object. Maybe the one that we are not so use to see is the “name” field. With it we will specify the column that will help us to open every single record instead of having to do it with just the External Id one. Similar as before we can also create the table with a single line, but we would not specify all characteristics as we can do before.

tables.add(DataSource.Table.get(‘NewHotelReservation’,’Name’,columns));

Query

This method will help us to make queries in the organization. It means SOQL and also in the User Interface.

But here we need to specify how can I retrieve and show a single record in the case that we click on the External Id or Name field link (if part of below code) and also how can I show all records if I open the object List View (else piece of code).


override public DataSource.TableResult query(DataSource.QueryContext context)
{
    if (context.tableSelection.columnsSelected.size() == 1
&& context.tableSelection.columnsSelected.get(0).aggregation == DataSource.QueryAggregation.COUNT)
    {
        List<Map<String,Object>> rows = getRows(context);
        List<Map<String,Object>> response =
DataSource.QueryUtils.filter(context, getRows(context));
        List<Map<String, Object>> countResponse = new List<Map<String, Object>>();
        Map<String, Object> countRow = new Map<String, Object>();
        countRow.put(context.tableSelection.columnsSelected.get(0).columnName,response.size());
        countResponse.add(countRow);
        return DataSource.TableResult.get(context,countResponse);
    }
    else
    {
        List<Map<String,Object>> filteredRows = DataSource.QueryUtils.filter(context, getRows(context));
        List<Map<String,Object>> sortedRows = DataSource.QueryUtils.sort(context, filteredRows);
        List<Map<String,Object>> limitedRows = DataSource.QueryUtils.applyLimitAndOffset(context,sortedRows);
        return DataSource.TableResult.get(context, limitedRows);
    }
}

Search

Similar as before, this method will help us to make searches that include SOSL as well as global searches throw the User Interface in the organization.


override public List<DataSource.TableResult> search(DataSource.SearchContext context)
{
    List<DataSource.TableResult> results = new List<DataSource.TableResult>();
    for(DataSource.TableSelection tableSelection : context.tableSelections)
    {
        results.add(DataSource.TableResult.get(tableSelection,getRows(context)));
    }
    return results;
}

HotelReservationProvider

Similar as before, this class must extend another, DataSource.Provider and override 3 methods. If you take a look at documentation, this class is also global, but you can see on below example that public is more than enough

public with sharing class HotelReservationProvider extends DataSource.Provider
{
    public HotelReservationProvider() {}
}

getAuthenticationCapabilities

This method help us to set up authentication credentials.

override public List<DataSource.AuthenticationCapability> getAuthenticationCapabilities()
{
    List<DataSource.AuthenticationCapability> capabilities = new List<DataSource.AuthenticationCapability>();
    capabilities.add(DataSource.AuthenticationCapability.ANONYMOUS);
    return capabilities;
}

In our case, as the google sheet is public, open to everybody, the authentication is anonymous, but we can determine others like

  • Basic
  • Certificate
  • OAuth

getCapabilities

This second method would allow us to determine what the Datasource is capable during a SOQL or SOSL.

override public List<DataSource.Capability> getCapabilities()
{
    List<DataSource.Capability> capabilities = new List<DataSource.Capability>();

    capabilities.add(DataSource.Capability.ROW_QUERY);
    capabilities.add(DataSource.Capability.SEARCH);

    return capabilities;
}

Although we can find some others like

  • ROW_CREATE
  • ROW_UPDATE
  • ROW_DELETE

getConnection

Finally the method that creates the connection making a call to our Conector class

override public DataSource.Connection getConnection(DataSource.ConnectionParams connectionParams)
{
    return new HotelReservationConnection(connectionParams);
}

How to retrieve data

If you copy-paste above code, it would not compile.

You will find that there is method, getRows, that doesn’t exist. Obviously the name is not the important thing. The most important is the content of the method.

Here is where you are going to define the data, all values you will show in your organization. To highlight, the returned list, a map where the key is the API name of the field that we want to populate and the value is that, a value we will assign to the field.

My advice is to start with some hardcode first, so you can see that all your code is working fine, and then move to read the google sheet.

private List<Map<String,Object>> getRows(DataSource.ReadContext context)
{
    List<Map<String, Object>> rows = new List<Map<String, Object>>();

    List<HotelReservationGoogleSheetReader.HotelReservation> reservations;
    reservations = HotelReservationGoogleSheetReader.readHotelReservations();

    Integer counter = 0;
    for(HotelReservationGoogleSheetReader.HotelReservation resv : reservations)
    {
        rows.add(new Map<String,Object>
                   {
                      'ExternalId' => resv.ItemId,
                      'DisplayUrl' => 'http://www.salesforce.com/hotelReservation/2015/',
                      'Name' => 'NHR-00000' + counter,
                      'EndDate' => resv.EndDate,
                      'GuestName' => resv.GuestName,
                      'Paid' => resv.Paid,
                      'Price' => resv.Price,
                      'RoomNumber' => resv.RoomNumber,
                      'SpecialRequirement' => resv.SpecialRequirement,
                      'StartDate' => resv.StartDate
                   });
        counter++;
    }
}

In my case, I have another class HotelReservationGoogleSheetReader where we will find an inner class HotelReservation with the data of the hotel. And a method readHotelReservations that will use google API to read sheet information and populate HotelReservations lines.

As part of your investigations, I will leave you to find how to read the google sheet, but I can show you a small piece of code. At the end I only make a Http request to get access to google and once I have its cells I will use Salesforce XMLStreamReader to iterate over there.

Apex Connector framework provides lot REST API examples in order to access Google Drive in general so take a look at them.

Http http = new Http();

HttpRequest reqCell = new HttpRequest();
reqCell.setEndpoint(CELL_URL);
reqCell.setMethod('GET');
reqCell.setHeader('content-type', 'application/atom+xml' );
reqCell.setHeader('X-If-No-Redirect', '1');
reqCell.setHeader('Authorization','AuthSub token="'+TOKEN+'"');

HttpResponse resCell = http.send(reqCell);
String bodyCell = resCell.getBody();

XMLStreamReader reader = new XMLStreamReader(bodyCell);

External Data Source creation

Once I have everything, it’s time to create the External Data Source and its External Object

Similar as before go to:

Setup | AppSetup | Develop | External Data Sources

and click on New.

This time, the Type drop down list will include your Provider class. So after selecting it, just click on Validate and Sync button and your new External Object will be created.

On below image you can find some other Providers, as I have more than one in my organization

Captura de pantalla 2015-12-19 a las 12.54.20

Summary

This post explain to you what is Apex Connector Framework and how to use it with a simple example.

You can also take a look at my latest session at Dreamforce’15 where I explain step by step how to use Apex Connector Framework – Make Anything a Salesforce Object

Now it is your turn to continue doing some investigations around this topic.

Mi primera Visualforce II

En la última entrada en Español estuvimos aprendiendo como empezar con Apex y Visualforce. Mostramos una nueva página con algunos campos del objeto Cuenta (Account). Hoy veremos cómo pasar de campos de lectura a escritura.

Lo primero de todo, intentaremos cambiar los campos que teníamos en la pantalla anterior a modo escritura. Para ello lo único que tenemos que hacer es utilizar un tag diferente: apex:outputField

En este caso, la página se llamará accountedit ya que la vamos a utilizar para modificar la información que ofrece.

<!-- accountedit -->
<apex:page standardController="Account">
  <apex:form >
    <apex:pageBlock title="Vista Cuenta">
      <apex:pageBlockSection title="Campos Cuenta">
        <apex:inputField value="{!account.name}"/>
        <apex:inputField value="{!account.site}"/>
        <apex:inputField value="{!account.type}"/>
        <apex:inputField value="{!account.accountNumber}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Si escribimos una Url similar a esta:

https://<instancia>salesforce.com/apex/accountedit

vemos como ahora tenemos unos cuadros de texto donde añadir la información.

Captura de pantalla 2015-11-27 a las 18.43.01

De igual forma que en la anterior entrada, si queremos ver la información de una cuenta en concreto para modificarla, debemos simplemente añadir el Id único de la misma, en la url.

Captura de pantalla 2015-11-27 a las 18.49.54.png

Si haces una búsqueda en Google y pones algo del tipo “añadir campo de escritura en mi visualforce” aparecerá otro tag que también podrías usar: apex:outputText

¿Cual es la diferencia entre ambos tag si el resultado es el mismo? La principal es que outputField va a respetar el tipo de campo que quieras mostrar, mientras que outputText añade eso, un texto. Veamos un ejemplo que es bastante claro.

<apex:page standardController="Account">
  <apex:form >
    <apex:pageBlock title="Vista Cuenta">
      <apex:pageBlockSection title="Campos Cuenta">
        <apex:outputText value="{!account.type}"/>
        <apex:outputField value="{!account.type}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

En el código de arriba vemos como queremos mostrar un campo de tipo picklist en nuestra página visualforce. Uno con outputField y el segundo es outputText. ¿Cual es el resultado?

Captura de pantalla 2015-11-27 a las 18.59.02.png

En campos de tipo lectura no podemos encontrarla ya que muestra simplemente el valor que hay almacenado en base de datos. Sin embargo en el caso de escritura, la diferencia es mayor

Captura de pantalla 2015-11-27 a las 18.59.50

Mientras que la en la primera opción muestra sólo un texto, el valor asociado a ese campo en el registro, en la segunda se puede desplegar el campo y visualizar todas las opciones que ofrece.

<apex:page standardController="Account">
  <apex:form >
    <apex:pageBlock title="Vista Cuenta">
      <apex:pageBlockSection title="Campos Cuenta">
        <apex:inputText value="{!account.type}"/>
        <apex:inputField value="{!account.type}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

Esto no significa que no debamos usar outputText ó inputText, simplemente que en determinados casos, es preferible ouputField / inputField, ya que proporcionamos una mejor experiencia al usuario final.

¿Intentamos añadir más información a nuestras primeras páginas Visualforce? Algo que también nos puede interesar es mostrar información de los objetos relacionados. De igual forma que la página standard de Cuenta muestra las Oportunidades que están relacionadas con esa Account, ahora queremos mostrar esas Oportunidades en nuestra página Visualforce. Para ello utilizaremos el tag: apex:relatedList

En este caso, el tag deberemos ponerlo fuera del tag apex:form y simplemente añadir el nombre API del objeto. A partir de ese momento, Salesforce se encarga de todo el trabajo por nosotros.

 <apex:page standardController="Account">
   <apex:form >
     <apex:pageBlock title="Vista Cuenta">
       <apex:pageBlockSection title="Campos Cuenta" columns="2">
         <apex:outputField value="{!account.name}"/>
         <apex:outputField value="{!account.site}"/>
         <apex:outputField value="{!account.type}"/>
         <apex:outputField value="{!account.accountNumber}"/>
       </apex:pageBlockSection>
     </apex:pageBlock>
   </apex:form>
   <apex:relatedList list="Opportunities" />
</apex:page>

Captura de pantalla 2015-11-27 a las 19.07.09.png

En el caso de que queramos tener la opción de modificar alguno de los valores des esta lista relacionada, necesitaremos añadir código Apex y un Controlador. Pero esto es algo que veremos en la próxima entrada. Hoy continuaremos sólo con acciones que podamos llevar a cabo con el Controlador Standard de los objetos.

Uno de los últimos pasos que vamos a dar hoy será guardar en base de datos las modificaciones que hacemos en uno de los campos. Para ello, añadiremos un botón a nuestra página mediante el tag: apex:commandbutton Revisa este tag porque es muy interesante, ya que con sus atributos puedes indicar si mostrar el botón solo en la parte de arriba de la pantalla ó en ambos lados (por defecto los muestra en los dos).

<!-- accountedit -->
<apex:page standardController="Account">
  <apex:form >
    <apex:pageBlock title="Vista Cuenta">
      <apex:pageBlockButtons >
        <apex:commandButton action="{!save}" value="Save"/>
      </apex:pageBlockButtons>
      <apex:pageBlockSection title="Campos Cuenta">
        <apex:inputField value="{!account.name}"/>
        <apex:inputField value="{!account.site}"/>
        <apex:inputField value="{!account.type}"/>
        <apex:inputField value="{!account.accountNumber}"/>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form>
</apex:page>

El atributo value nos permite indicar qué nombre le queremos poner al botón y el atributo action llamará al proceso que queremos ejecutar. Como seguimos trabajando con el Controlador Standard del objeto, simplemente debemos añadir “save” y Salesforce se encarga de realizar la acción de guardado por nosotros.

Captura de pantalla 2015-11-27 a las 19.13.21

Y después de modificar el valor de Account Number por 124 y darle al botón Save, accountview page muestra:

Captura de pantalla 2015-11-27 a las 19.16.51

¿Qué otras acciones nos permite Salesforce? En este link encontramos información sobre ellas, pero básicamente son:

  • Save
  • QuickSave
  • Edit
  • Delete
  • Cancel
  • List

 

Por ultimo, una ayuda para tener que evitar modificar el id de la cuenta cada vez que queremos mostrar un registro distinto. Si hemos creado estas páginas es porque queremos ofrecer esta vista al usuario final en vez de la standard. En ese caso, Salesforce nos permite sobre escribir estas vistas en el objeto.

Si vamos a:

Setup | Customize | Accounts | Buttons, Links and Actions

Modificaremos las vistas View y Edit

Captura de pantalla 2015-11-27 a las 19.20.23

En el caso de que queramos hacer lo mismo con un objeto creado por nostros, custom, en vez de Standard, solo tenemos que ir a

Setup | Create | Objects | <selecciona tu objecto> 

Y desde ahí baja a la sección Buttons,Links and Actions. La sobre escritura de la página se haría de forma similar a un objeto Standard.

En este post hemos aprendido:

  • Como crear una página editable
  • Diferencia entre inputField vs inputText y outputField vs outputText
  • Como añadir lista de un objeto relacionado a Cuenta
  • Como guarder modificaciones en la página de editado
  • Como evitar modificar la url con el id cada vez que quiero mostrar un registro

Y todo esto sin añadir una línea de código en Apex, solo Visualforce, ya que hemos usado el controlador Standard que los objetos nos ofrecen.

Data Pipeline and Salesforce – Do not break the rules

Captura de pantalla 2015-09-14 a las 16.19.20

Salesforce is working in a new feature called Data Pipeline that help us to integrate Apache Pig into Salesforce. Previous link can give you lot of information about what is Apache Pig but basically it is a Open Source technology that gives you mechanism for parallel processing of MapReduce jobs in a Hadoop cluster.

Here we can find 2 main keywords:

  • MapReduce: Software framework to write programs to execute large amount of unstructured data in parallel.
  • Hadoop Cluster: A special type of cluster that helps you to analyze and store large amount of unstructured data.

At the end we will say that:

Data Pipeline will help you to execute processes with large amount of data in parallel in Salesforce. 

Ok, I know what you are thinking right now. It sounds interesting but, I can get the same with other Salesforce features like @future, Queueable or Batch Apex. And that’s right. But the main thing here is that performance is much better and instead of having to wait maybe an hour in order to complete an asynchronous execution, Data Pipeline provides results in few minutes.

And how can we use it in Salesforce? We only need to create a single Pig Script using Pig Latin language (forget Apex) via Developer Console. And once we have it, click on Submit Data Pipeline button to execute the process.

Captura de pantalla 2015-09-11 a las 18.29.00

If you already know Apache Pig probably you know how to use Pig Latin and this pilot will be quite simple for your. But if this is not your case, I would like to start with 2 examples.

One of them will show you how to insert records and avoid getting an unexpected situation due to a validation.

The second one will help us to create records related by a Master – Detail relationship.

1st Use Case – How to create records avoiding breaking Salesforce rules

I have a new custom object called Header__c and I need to create some records taking Opportunity records as resource. The Pig script will look like this one. Something simple, where I retrieve some fields from Opportunity object and after doing a filter by Opportunity Name, store the result in the new Header custom object record.

Captura de pantalla 2015-09-10 a las 10.49.50

As you can see, the result is successful.

Captura de pantalla 2015-09-10 a las 10.50.03

And this is the new record in the list view

Captura de pantalla 2015-09-11 a las 17.38.16

But what does it happen if Header object has a validation that avoid any insertion if amount is not greater than 100? This is the validation rule:

Captura de pantalla 2015-09-10 a las 10.52.07

And if we run the same script, but using Opportunity2 as a source, this is what developer console returns.

Captura de pantalla 2015-09-11 a las 17.43.32

Ok, what is going on? The process finishes successfully. And what about the list view?

Captura de pantalla 2015-09-11 a las 17.38.16

Same as before. Just a single line. So how can we explain this?

We need to remember that Data Pipeline is going to execute records in batch, so if there is any failure during the script execution, and a record cannot be inserted, the process will continue with the rest, and finish successfully. We could compare it with Batch Apex, where the Apex Job screen shows us that an execution has finished properly without errors even if this means that some records where not inserted due to some validations that we have added in our code.

And how can we avoid this situation and not break the rules? Imagine this use case. This time we have 2 opportunities with the same name, but one of them has an amount less than 100.

Captura de pantalla 2015-09-11 a las 17.54.59

If I execute above script, just filtering by the new Opportunity Names, just one of them will be inserted

Captura de pantalla 2015-09-11 a las 18.05.29

But do we have a nicer and cleaner way to do it? We can also filter by amounts so we will avoid hitting the validation:

Captura de pantalla 2015-09-11 a las 18.22.44

Getting the below result

Captura de pantalla 2015-09-11 a las 18.26.18

2nd Use Case – Create a Master-Detail relationship

Another common situation is the use case where we need to create two records related by a Master-Detail relationship.

With above code examples we can think that this is not a big deal, but we have to keep in mind that this Pig Scripts doesn’t provide handle errors on the current release, and if for any reason the header is not created and we try to create lines related to them, the process can fail without our knowledge (as we show before).

So how can we solve this situation? Salesforce advice is to create 2 scripts, so after running the one that create Headers, we can run another that will create records of my new custom object called Line__c.

Captura de pantalla 2015-11-16 a las 19.34.36

What can we find on above script?

  • First of all we look for all Headers that we have in the Organization right now.
  • Look for all Opportunity Lines we have in the Organization as well.
  • Execute a Join by Opportunity Id, as both, Header__c and OpportunityLineItem have this field.
  • Finally I will sort the joined result, because we don’t need all fields for my new Line__c registers.
  • And Store, save, the information in the custom object.

And this is the result:

Captura de pantalla 2015-11-16 a las 19.41.25

Yes, so simple. Something to keep in mind? List fields on Store in the same order as we have defined in the variable that we will use for the population.

But looking at this code, we can think, why don’t do it in the same script? Remember that the process is asynchronous, so by the time we query Header__c records, maybe they are not inserted yet in the organization.

Anything to highlight?

  • Salesforce Data Pipeline is still in Pilot so in order to use it you need to ask Salesforce to give you access to it.
  • When will it be GA? My latest news, Summer ’16 but as always, Safe Harbor.
  • Cost? Price? Yes, it will have an extra cost apart from the licenses, but I don’t have the information to tell you how much.
  • Advantages?
    • Salesforce ensures that these executions would be faster than other asynchronous processes like Batch Apex or @Future.
    • Running Apache Pig into Salesforce help us to do this execution in a multi-tenant environment. However if we run a process in Apache Pig outside of Salesforce, executions are for a single user.
    • As we are not using Apex, we will not hit governor limits.
    • We will be able to add these scripts into packages so our customers can also run these processes.
  • Disadvantages?
    • We cannot create scripts with more than 20 operators, 20 loads and 10 stores.
    • Although we will not hit governor limits, we need to keep in mind that Store (Insert / Update) is done via Bulk API, so any restriction still apply.
    • Unfortunately we cannot call Pig Scripts from Apex. Only from Developer Console and from API 33.0 onwards, during deployment.
    • It doesn’t offer a debug tool, so if we are in the first use case and a record is not inserted, it would not be easy to find the reason quickly.
    • You cannot handle errors easily, it means, you cannot add something in your code like try / catch.

But this is not all. If you want to know more about Data Pipeline, you can also look at the presentation that Carolina Ruíz and myself run at Dreamforce ’15. Find the video Data Pipelines: Big Data Meets Salesforce.

External Objects – What Lightning Connect offers to us

On my previous post, I talked about Lightning Connect and tried to explain with the Checa Hotel example how it worked.

This post is going to explain what you can do with one of the most important elements of Lightning Connect: External Objects.

What is an External Object?

This is a new type of object that will help us to visualize the information that we have in a external repository after creating a connection between Salesforce and this repo.

As a quick reminder from the previous post. We will be able to create an External Data Source linked to the External place and we will be able to create one or many External objects from there.

Captura de pantalla 2015-10-03 a las 19.00.55

How can we define it?

Captura de pantalla 2015-10-17 a las 11.35.08

These external objects will be created:

  • Automatically: via the Validate and Sync button that we can find in the External Data Source.
  • Manually: via its entry on the menu.

But in any case my advice is to take advantage of the automatic creation and use the manual one just in case you need to modify something.

How does an External Object look like?

We are going to continue with the same example as the previous post, the Checa Hotel reservations. And this is the external object.

Captura de pantalla 2015-10-19 a las 15.04.17

At a first sight it is similar to a custom one.

External Object vs Custom Object – Differences:

  1. API Name: It ends with __x instead of __c
  2. External Data Source: This field is populated with the External Data Source from where we have created the object. This field is required if you create the object manually.
  3. Table Name: This name is automatically created when you click on Validate and Sync button and is the name of the table found in the external repository once we have created the connection. This field is required if you create the object manually.
  4. Name Field: Here we will specify the field that will help us to open every single record when we create a tab related to this object. At least I couldn’t find a way to modify the value of this field, and the default value is the External ID standard field. However I can also say that if you create the connection via a Custom Connector instead of using Heroku, for instance, this value could be a different one. But this is something we will talk about in a future blog entry.
  5. All checkboxes that you can find on below image and belongs to a Custom object, here are missed. So we can now know that Reports are not provided. That could sound disappointing to some of you, but remember that this can change in the future, and also, you can always move this external object data to a custom object and create these reports and dashboards that you need.

Captura de pantalla 2015-10-17 a las 12.04.48

External Objects Sections:

Standard Fields

Captura de pantalla 2015-10-19 a las 15.08.41

I know what you are thinking. Where are the other fields like Created By, Last Modify By etc. ? Remember that these records till Summer’15 are not writable, so we don’t really need this information. We are just showing what we have in the external repository.

Here we will only find 2 fields:

  1. External ID: This field will help us to keep the uniqueness across the whole organization. This does not mean that the ID concept that Custom Object has are not applicable here. These records will also have an ID and we will see it later, but at the same time this External Id is required.
  2. Display URL: This is a link and if you click on it, in my case where I have read the information from the Google Spreadsheet, I will have the data in XML format of the record where I clicked this field.

Captura de pantalla 2015-10-17 a las 12.31.18

So, if we click on the Display URL field, we would get this information:

<?xml version='1.0' encoding='utf-8'?>
    <entry xmlns="http://www.w3.org/2005/Atom" 
           xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" 
           xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" 
           xml:base="http://<namehavebyheroku>.herokuapp.com/InMemoryProducerExample.svc/">
      <id>
         http://<namehavebyheroku>.herokuapp.com/InMemoryProducerExample.svc/HotelReservation(1)
      </id>
      <title type="text"/>
      <updated>2015-10-17T10:31:28Z</updated>
      <author>
          <name/>
      </author>
      <link rel="edit" title="HotelReservation" href="HotelReservation(1)"/>
      <category term="InMemoryProducerExample.HotelReservation" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
      <content type="application/xml">
          <m:properties>
               <d:StartDate m:type="Edm.DateTime">2015-09-01T00:00</d:StartDate>
               <d:Price m:type="Edm.Double">125.0</d:Price>
               <d:ExternalId m:type="Edm.Int32">1</d:ExternalId>
               <d:Paid m:type="Edm.Boolean">true</d:Paid>
               <d:EndDate m:type="Edm.DateTime">2015-09-03T00:00</d:EndDate>
               <d:GuestName>Anne Kalen</d:GuestName>
               <d:RoomNumber m:type="Edm.Int32">1</d:RoomNumber>
               <d:SpecialRequirement>Balcony please</d:SpecialRequirement>
          </m:properties>
     </content>
    </entry>

Custom Fields & Relationships

This is the section that looks like more similar to a custom object. Here we can find all the fields that the object will have. We will be able to define different types and different features. And if my Heroku application didn’t allow me to specify all that I really needed, I can make any change manually just clicking the Edit link beside the field.

Captura de pantalla 2015-10-17 a las 12.15.04

What could we miss?

We don’t see any relationship on above image, but we can really create Lookups for instance. What we would not allow to do is to create a Master-Detail relationship. But Salesforce give us a couple of workarounds to solve this situation.

  • External Lookup: This field type will help me to link 2 external objects, so if for any reason I have 2 different objects in my google spreadsheet and now I can see both in my Salesforce organization, I can link them via this field type that must be created manually because this is a Salesforce type that Java is not aware of it.
  • Indirect Lookup: This field type will help me to link an external object to a custom or standard one. Let’s talk more about this type with an example.

Captura de pantalla 2015-10-03 a las 20.12.08

Based on this image, I want to have somehow more information about my guests, in this case, Kim Rosenber. So that, I can save her information in order to send information about the hotel when we have special offers, or give her special discounts based on the number of reservations she has made with us. Because of that, I will create an Account per guest.

Captura de pantalla 2015-10-17 a las 13.03.29

Now, I would like to be able to click on my Guest Name in the HotelReservation record and go directly to the Account. Also I would like to be able to get all reservations related to each Account, so that, I can decide if this person deserves a special discount.

How can I get it? First of all we will need to create a field on Account object. The only requirement is that it should be External Id and it must be unique.

Captura de pantalla 2015-10-17 a las 13.26.29

Captura de pantalla 2015-10-17 a las 13.12.47

Secondly, we need to populate it with the information of my guest. This data should be the same as I have in the external object, so this time would be the name.

Finally on the External Object we need to modify the Guest Name field type from Text to Indirect Lookup and link it to Account object. As a tip, if for any reason, you don’t create the field on Account properly, it will not appear in the drop down list and you would not have the option to link your Hotel Reservation to Account.

Captura de pantalla 2015-10-19 a las 15.25.39And how does it look like?

Guest name has a link instead of being a simple Text field

Captura de pantalla 2015-10-17 a las 15.43.58

And if we add the related list in the Account object, we can also check all reservations that this person has made

Captura de pantalla 2015-10-17 a las 15.48.35

We could think that this is similar to usual Lookup fields. The main difference is that in this case I can click on the real Guest Name and navigate to the account. However, with a Lookup, we will need to have an Id value in the External Object in order to make the navigation possible.

Page Layout

Like custom objects, this section helps us to modify the look and feel of my record, changing the way that fields are sorted, if I want to add a new section, or blank space.

Captura de pantalla 2015-10-17 a las 12.15.54

Compact Layout and Search Layout

Simple, we have the option to create new Compact Layouts and determine if I require to add any custom button in the List View.

Captura de pantalla 2015-10-17 a las 12.16.23

Buttons, Links, and Actions

We can also perform another actions with these external objects. How? For instance, creating a button with some Apex Code in the background. At the end we could work with them as they were custom or standard objects.

Now, the use case is that every time a guest leaves the hotel, we need to print the invoice in pdf format.

Let’s think that we are doing this job for a custom object. So what should we do first?

  • Create a Custom Controller
public with sharing class HotelReservationController
{
   public ApexPages.StandardController controller;
 
   public Decimal amount {get; private set;}
   public Decimal totalAmount {get; private set;}
   public HotelReservation__x reservationInfo {get; private set;}
 
   public HotelReservationController(ApexPages.StandardController stdController)
   {
     controller = stdController;
     Id reservationId = controller.getRecord().Id;
     calculateReservation(reservationId);
     amount = calculateAmount(reservationInfo.Price__c);
     totalAmount = calculateTotalAmount(reservationInfo.Price__c);
   }
 
   public void calculateReservation(Id reservationId)
   {
     HotelReservation__x reservation = [Select EndDate__c, 
                                               GuestName__c, 
                                               Price__c, 
                                               RoomNumber__c, 
                                               StartDate__c 
                                         From HotelReservation__x
                                         Where Id = :reservationId
                                         Limit 1];
     reservationInfo = reservation;
   }
 
   public Decimal calculateAmount(Decimal amount)
   {
     return amount.setScale(2, RoundingMode.HALF_UP);
   }
 
   public Decimal calculateTotalAmount(Decimal amount)
   {
     Decimal tax = (amount * 21) / 100;
     Decimal totalAmt = amount + tax;
     return totalAmt.setScale(2, RoundingMode.HALF_UP);
   }
}

As you can see on above code, the controller is really simple. It starts getting the Id of the record so YES!! we can also use getRecord() method with External Object.

And continue doing some calculations. The most important thing is the method where we run a SOQL query. HOW?? Yes, even we don’t have any data stored in our organization, Lightning Connect will allow us to perform such actions as well as SOSL. Also I would like to highlight that even we filter by the Id that we retrieved from the Controller, we would be able to use the External ID field as this is also have a unique field value.

  • Create a Visualforce page

My Visualforce page will be a pdf with the invoice for the guest. First of all the Standard Controller will be the External Object. And secondly we will extend the functionality with the Controller that we created above. The rest, simple, just call Controller methods and fields in order to show data that we need.

Captura de pantalla 2015-10-20 a las 8.30.28

So, is it not allowed to use visualforce tags like <apex:outputField> ? Of course you can use them. Imagine you want to create a Visualforce page with some Reservation data, go for it!! it will show the name of the field as well as the type that belongs to the data. However as this is a pdf, my option was to use directly divs with all data.

  • Create a button linked to the Visualforce page that we have just created and add it to the External Object Page Layout.

Captura de pantalla 2015-10-17 a las 16.02.53

At the end, this is the result. After clicking on the Invoice Print Invoice button

Captura de pantalla 2015-10-17 a las 16.54.35

We get this pdf ready to print and share with the customer

Captura de pantalla 2015-10-17 a las 16.53.14

Field Sets

We can also create a field set related to our External Object and show it in a Visualforce page, for instance.

My field set will not provide different information from the current page layout, but this is a good example to show how Field Sets also works.

First of all we will create the Field Set

Captura de pantalla 2015-10-17 a las 17.15.50

Secondly we are going to create a Visualforce page that we will use to override View mode on this External Object.

<apex:page standardController="HotelReservation__x" extensions="HotelReservationController">
  <apex:form >
    <apex:pageBlock title="Hotel Reservation">
      <apex:pageBlockSection title="Information">
        <apex:repeat value="{!fields}" var="f">
          <apex:outputField value="{!HotelReservationFieldSet[f.fieldPath]}"/>
        </apex:repeat>
      </apex:pageBlockSection>
    </apex:pageBlock>
  </apex:form> 
</apex:page>

Similar as before we will use the HotelReservation__x as standard controller and the extension will be the same as before. This time we are going to show dynamically all fields that we have included in the FieldSet. As right now we can find all of them, this is a good page to override the view mode of any Hotel Reservation record. If in the future we need to include more fields or make some modifications, code will be exactly the same, because, as I said before, we get data dynamically.

In the controller we have just added these 2 methods and their calls in the constructor:

public List<Schema.FieldSetMember> getFields()
{
  return SObjectType.HotelReservation__x.FieldSets.ReservationInformation.getFields();
}
 
public HotelReservation__x calculateReservationFieldSets(Id reservationId)
{
  String qry = 'Select ';
  for(Schema.FieldSetMember f : getFields())
  {
    qry += f.getFieldPath() + ', ';
  }
  qry += 'Id, ExternalId From HotelReservation__x Where Id = :reservationId limit 1';
  return Database.query(qry);
}

And this is the result

Captura de pantalla 2015-10-18 a las 8.41.03

What else can I do?

If you are one of these persons that usually works with portable devices like tablets and smartphones, you don’t need to worry about this new type of objects because Salesforce1 app and Lightning Connect also allow you to see this data.

IMG_0305

IMG_0306

Limitations

Finally I would like to highlight some limitations (based on Summer ’15 release)

  • We can only have 100 External Objects per org
  • It doesn’t allow Master-Detail relationship, but as we show above, Lightning Connect offers 2 workarounds, External Lookups and Indirect Lookups
  • We cannot create reports and dashboards with their information. If this is really required for us we can always move the data to a custom object using some apex code, and then create the report from this object.
  • Triggers are not allow. I think that the reason is that actually we are not able to control what can happens outside of Salesforce.
  • Although we can perform SOSL and SOQL, we still have some limitations, like For Update is not allow today.
  • These objects are read only. It means, we cannot edit it and see the change in my google drive, for instance. I have already realized that Winter ’16 give us this functionality, but this is something that I will talk about in a future entry after doing some more researches.

I hope this post was useful for you and understand better what you can do with these new object types and how.

Lightning Connect – Visualize your external data in Salesforce easily

By Christmas 2013, I received a fantastic present. FinancialForce.com platform team was starting to take a look at one of the new pilots that Salesforce had. By that time we called it “External Data Source” or “External Objects” however, nowadays you know it as Lightning Connect.

And here I am, writing about this feature that was released during Dreamforce ’14 and after having talked about it on such amazing event, Madrid ’15, London ’15 and finally few weeks ago at Dreamforce ’15, now, it’s time to share my knowledge via a blog entry.

So first of all, what is Lightning Connect? This feature allows you to link an external repository, Google Drive, SharePoint, Amazon and others, to your Salesforce organization, therefore, you can visualize and use the data that you have outside of your org. At this moment maybe you are thinking about an ETL (Extract – Transform – Load) but Salesforce already have features like Apex Data Loader. So keep in mind. This is not an ETL.

How does it work? The platform makes http requests to the External Repository and its information is sent back to the org in Real Time via an OData protocol.

Captura de pantalla 2015-05-23 a las 19.33.18

It could sound too technical or complicated if you are not a developer, even it could make you give up and think that Data Import Wizard will help you to solve your scenario. So let me show you a comparation of what you would need to do if you use these tools.

Data Import Wizard vs Lightning Connect

Data Import Wizard

  1. Create Custom object and / or fields
  2. Save data in a csv file
  3. Launch Import Wizard
    1. Choose the data
      1. What do I need? Create or Update
      2. Select the csv file
    2. Edit mapping
    3. Start Import

If you need to modify a record, you would have to run all sub steps that belongs to point 3.

Lightning Connect

  1. Create the connection
  2. Synchronize metadata that means create automatically the object and its fields
  3. Create a Tab
  4. Refresh the List View

Any time you need to make a modification outside of your organization, you should only need to refresh the page to see the changes in Salesforce.

Checa Hotel – Use Case

Sometimes it is easier to understand things when we see a real use case. So lets go for it.

Checa Hotel saves its reservations in a spreadsheet:

Captura de pantalla 2015-10-03 a las 18.53.03

But by the time they started moving into Salesforce, they continued using Google Drive for a while. In any case they wanted to avoid the double effort that means add reservations to the google spreadsheet and move data into Salesforce every time that a guest make a new reservation.

How can Lightning Connect help?

This Salesforce feature will help us to show in our organization any change in the Google Spreadsheet. How?

Lightning Connect is a feature that via OData protocol help us to link the external repository with your Salesforce organization. Per connection, you can create a single External Data Source that will have 1 or many External Objects. At the end this External Object is the metadata that we are going to use to show the data that is stored in the Google Spreadsheet in our case.

Captura de pantalla 2015-10-03 a las 19.00.55

What do we need to get it? We only need to follow above steps.

1. Create a Connection

1.1. Get the URL

There are different ways to get this url. At the end we need to create the connection we have explained above. And we can do it with an existing OData provider, like JitterBit, or we can create something with code. This is my case, where I have used Heroku in order to create an application in Java that is able to read a google sheet via its API.

As a start point you can check odata4j. Actually, my Java application started with some code that I pasted from this page.

So basically you only need to create your producer and register some data there. Bellow example it will just show numbers from 0 to 20.

//InMemoryProducer is a readonly odata provider that serves up POJOs 
//as entities using bean properties
//call InMemoryProducer.register to declare a new entity-set, 
//providing a entity source function and a propertyname to serve as the key
final InMemoryProducer producer = new InMemoryProducer("InMemoryProducerExample", 1000);
		
//expose an large list of integers as an entity-set called "Integers"
producer.register(Integer.class, 
                  Integer.class, 
                  "Integers", 
                  new Func<Iterable>()
                  {
			public Iterable apply()
			{
                          return Enumerable.range(0, 20);
			}
		  }, 
                  Funcs.method(Integer.class, Integer.class, "intValue"));

So we need to do something similar but with the data that we read from google spreadsheet.

In order to do that, the first thing is the creation of an inner class where we will store the data

public static class Reservation
{
   private Integer externalId;
   private Integer roomNumber;
   private Date startDate;
   private Date endDate;
   private Double price;
   private Boolean paid;
   private String guestName;
   private String specialRequirement;
		
   public Integer getExternalId(){ return externalId; }
   public void setExternalId(Integer value) { externalId = value; }
  
   ... 
   //getter and setter are required to be able to read info in Salesforce
}

The next step is to have a method that reads the data

public static List readGoogleReservations(SpreadsheetService googleService) 
{
   List reservationInfo = new ArrayList();
   URL feedUrl = null;
   feedUrl = new URL("https://spreadsheets.google.com/feeds/spreadsheets/private/full");
   ...
   return reservationInfo;
} 	

And this method will be called from my producer:

producer.register(Reservation.class,
 		  "HotelReservation",
	 	  new Func<Iterable>()
	 	  {
	 	     public Iterable apply()
	 	     {
                        try
                        {
	 		   return readGoogleReservations(googleServicefinal);
                        } 
                        catch (Exception e) { return null; }
                     }
	 	   },
                  "ExternalId");

1.2. Create the External Data Source

Once you have your Heroku application and the url that it returns, the rest is just #clicksnotcode.

You only need to go to External Data Source entry on the left side bar menu under Setup:

Captura de pantalla 2015-10-03 a las 20.13.39

And click on New. Then just enter information about the new Connection.

Captura de pantalla 2015-10-03 a las 20.09.18

As you can see on above image, it is really simple. Just provide the Name and Type chose OData 2.0 that is the one we have used in our Heroku application.

Then, populate the Url field with the application one, that looks like

http://<funnyname_provided_by_heroku>.herokuapp.com/<yourProducer>.svc

And that is all. Just save, and your connection will be created.

In the case that you are using a different way to create the connection, you would only need to select a different type from the drop down list. For instance on below image, we can also have the Salesforce option, just a Simple URL, and the Custom one that is called LoopbackDataSourceProvider. This Custom one is related to a Custom Apex Connector, another interesting topic that I will talk about in a future entry.

Captura de pantalla 2015-10-03 a las 20.10.04

2. Synchronize Metadata – Create External Object

The next step is the creation of the Metadata that will contain the information we want to show. Even we can use the External Object entry that we can find on the left side bar menu, and create it manually, my advice is to use the External Data Source and create it automatically. Just clicking on Validate and Sync button, it will read those objects that the connection provides and create them for you.

Captura de pantalla 2015-10-03 a las 20.09.51

And if we open HotelReservation one, we can check that it looks like a custom object.

Captura de pantalla 2015-10-03 a las 20.08.58

3. Create a new Tab

Finally I want to see the data in my organization, so I will just create a Tab for it and update the look and feel of the list view.

Captura de pantalla 2015-10-03 a las 20.10.38

Also, if I want to open any of the records, we will see the data as well.

Captura de pantalla 2015-10-03 a las 20.12.08

And that’s all, we have our Google Spreadsheet links to our Salesforce organization.

4. Refresh the List View

Something that I have mentioned since the very beginning is that one of the advantages of Lightning Connect is that any change outside will be reflected in Real Time in my org. And the best way to show it is with a video.

Hightlights

I would like to finish this entry with some bullets points that could be interesting for you

  • Limitations
    • Lightning Connect has an additional cost. How much? Please contact Salesforce.com for more information.
    • We can only have 100 external objects per org
    • It is Read Only for now, although Winter’16 brings the writable feature. Let see how it works in a future entry🙂
    • Callouts: At the end we are making calls to google drive, so this limitation is still in place. But if you need to increase it, contact Salesforce because this is a soft limitation.
    • External Objects don’t support Master-Detail relationships but Lightning Connect offers workaround for that. Follow next entries where I will talk about what you can do with an External Object.
    • It doesn’t provide Reports for now.
  • Advantages
    • Once you have the Url, the configuration is #clicksnotcode
    • It provides to end user Real Time refresh so if you are worried about performance, just keep in mind that the first time you open your External Object tab, it will take a bit longer because it is doing the connection. But from that moment, any update will be as fast as your internet connection allows you.
    • Finally, something that can make you think that pay for it, worth it. It doesn’t count against the storage limitation because we are not saving anything in Salesforce. We are just showing what we have outside. It is like a mirror.

Captura de pantalla 2015-10-03 a las 20.51.19

I hope now you can understand a bit better Lightning Connect and all its benefits.

Enjoy and see you in my next entry.