Tuesday, August 21, 2012

Counting a collection in Dreamweaver Templates


Many times in your Dreamweaver Template you want to count the number of field values of a multivalued field, or the number of Components in an array.

There is a way to count a collection in the Package, but the documentation regarding the built-in functions available within Dreamweaver templates is not very extensive.
Before you start writing a .NET Template that counts a collection and pushes this number into a Package Item, take a look at:
[TemplateCallable]
public int CollectionLength(string expression)

The expression passed to the method equals the fully qualified name of the Package Item and a value selector.
The following example will return the number of values of the paragraphs field.
@@CollectionLength("Component.Fields.paragraphs")@@

Sunday, August 19, 2012

Common issues while installing UGC

I wanted to share with you the issues that I encountered while installing and configuring SDL Tridion UGC. They seem to occur often, and I hope this speeds up the process when you encounter them as well.


Setting up the Content Delivery Web services

One Content Delivery Web service is setup for the content manager, and one for the websites visitors, and both have a storage configuration setup solely for the UGC database:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration Version="6.1"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="schemas/cd_storage_conf.xsd">
    <Global>
        <ObjectCache Enabled="false">
            <Policy Type="LRU" Class="com.tridion.cache.LRUPolicy">
                <Param Name="MemSize" Value="16mb"/>
            </Policy>
            <Features>
                <Feature Type="DependencyTracker" Class="com.tridion.cache.DependencyTracker"/>
            </Features>
        </ObjectCache>
        <Storages>
            <StorageBindings>
                <Bundle src="ugc_dao_bundle.xml"/>
            </StorageBindings>
            <Storage Type="persistence" Id="ugcdb" dialect="MSSQL" Class="com.tridion.storage.persistence.JPADAOFactory">
                <Pool Type="jdbc" Size="5" MonitorInterval="60" IdleTimeout="120" CheckoutTimeout="120" />
                <DataSource Class="com.microsoft.sqlserver.jdbc.SQLServerDataSource">
                    <Property Name="serverName" Value="[Server Address]" />
                    <Property Name="portNumber" Value="1433" />
                    <Property Name="databaseName" Value="Tridion_Ugc" />
                    <Property Name="user" Value="[Username]" />
                    <Property Name="password" Value="[Password]" />
                </DataSource>
            </Storage>
        </Storages>
    </Global>
    <ItemTypes defaultStorageId="ugcdb" cached="false"/>
    <!-- Specifies the location of the license file. -->
    <!-- License Location="c:/Tridion/config/cd_licenses.xml"/ -->
</Configuration>

When you encounter problems with POST requests to the UGC CD Web service from the content manager, then enable tracing in the Tridion.UGC.Model, by replacing the assembly with the assembly from the trace folder:

Tridion\web\WebUI\WebRoot\bin\Tridion.UGC.Model.dll

The Trace log can be found at the same location in the Tridion.Web.Trace logfile and shows you the exact response from the CD Web service and a possible exception stack trace.

Also enable exception details and service metadata on http get for the UGC CD Webservices (system.serviceModel/behaviors) to allow easier debugging:


<serviceBehaviors>
    <behavior name="ODataServiceBehaviors" >
        <!-- Add the following element to your service behavior configuration. -->
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true"/>
    </behavior>
</serviceBehaviors>


Storage Type Exceptions

When you receive exceptions that are related to the storage configuration, the problem is most likely a malformed cd_storage_config.xml file, but it can also be a problem with your license.

To ensure that the right licenses file is used you can configure the location of the license file in the License element, or exclude this element and place the cd_licenses.xml file in the config folder.

When there is a licenses problem it will show up in the cd.core log file, when the log level is set to WARN.


NB: To easy debugging issues it is recommended to temporarily set the log level to DEBUG/TRACE.


Couldn't retrieve data from 1 datasources

When you get a “Couldn’t retrieve data from 1 datasources” exception when switching to the Comments tab in the Page or Component edit forms then you most probably have a problem in the Content Delivery Web service endpoint configuration.

This is configured in: Tridion\web\WebUI\Models\UGC\Configuration\DataSources.xml

<DataSource>
    <Key>1</Key>
    <Match>^(ugc:|oe:|tcm:)+</Match>
    <Url>[URL to UGC CD Webservice configured for the CM]</Url>
    <TimeOut>100000</TimeOut>
    <Threshold>0</Threshold>
    <RatingMinimum>-1</RatingMinimum>
    <RatingMaximum>5</RatingMaximum>
    <OAuthEnabled>false</OAuthEnabled>
    <Locale>en-us</Locale>
    <ClientId></ClientId>
    <ClientSecret></ClientSecret>
    <EventSystemClientId></EventSystemClientId>
    <EventSystemClientSecret></EventSystemClientSecret>
    <AccessTokenUrl></AccessTokenUrl>
</DataSource>

When you cannot post any messages

At one point I was able to query the OData web services using Linqpad, and I was able to access the comments tabs through the content manager explorer. However, I was not able to post any new messages. When this is the case verify that you configured the Ambient Data Http Module in the Content Delivery Web services web configuration.

<system.webServer>
    <modules>
        <add type="Tridion.ContentDelivery.AmbientData.HttpModule" name="AmbientFrameworkModule" preCondition="managedHandler" />
    </modules>
</system.webServer>

Users registered with ID null

Make sure that the UGC ambient cartridge is configured on both the UGC CD Webservice as the (staging or live) website. If this is not done users will be registered with ID null.


Using the ItemStats object


The ItemStats object (Tridion.ContentDelivery.UGC.Web.Model.ItemStats) allows you to access statistics on user generated content for a specific component or page. But using this object is not as clear as it seems.

When rendered, the UGC ItemStats User Control (<ugc:ItemStats />) adds the ItemStats object to an Item of the HttpContext.Current.Items collection using the name set in the Var attribute.

When you want to use this object you have to retrieve if from the context variable first. The UGC Item Stats control does not give you access to it.

NB: The Var attribute is optional, and defaults to ugcItemStats.

Friday, August 10, 2012

Extending the Content Manager

After a long time of silence I will start blogging about the different extension points of the SDL Tridion Content Manager.

Some of them are well documented, others are not, but with all of them there are best practices and issues to solve.

With this series I hope to share my knowledge with you, and help you get started. When you have questions, don't hesitate to ask for help, but please ask them on StackOverflow! Then your question, and its resolution immediately helps the entire community.

On the topic of community and knowledge sharing, I would like to take the opportunity to encourage everyone to commit to the proposed SDL Tridion Q&A website for Developers and Administrators on StackExchange Area 51! We’re currently (aug 8) at 66%, so we can use all the support we can get.



That said, let’s move on to the first extension point in this series, Search Indexing Handlers.

Extending the search index is a so called undocumented extension point. These points are not necessarily or primarily designed to be an extension point in implementations, but never the less the SDL Tridion architecture does allow it to, in practically the same way as the default elements of the system are implemented.

These extension points include:


  • Search Indexing Handlers
  • Resolvers
  • Renderers

Besides these points, Application Data is another useful feature to support you writing extensions.


Let me know when you have any topic suggestions or feedback on the articles.

Wednesday, August 08, 2012

Search Indexing Handlers


Introduction

Lets start with a business case to give you an idea when to use custom search indexing handlers.

When a Component with localized children is changed, all translations need to be updated as well. This however is not visible in the content manager. So, while working on a proof of concept assignment for a bank, they wanted me to show that we could write an extension to support their translation process.

It had to be possible to easily see and work with all Components that require translation:

  • When a parent is updated all localized children require translation
  • When a shared item is localized it requires translation
Usually this kind of metadata is stored as Metadata on the Component. However, I did not want editors to be able to change this information, and I did not want to impose requirements on the content model (Schemas), just because of this extension.

Therefore I decided to store the Translation Metadata as Application Data on the Components.

But since Application Data is not indexed I had to implement a custom search indexing handler that would index the Translation Required field on a Component, allowing me to find all Components that require translation.

So when you want to find items based on specific metadata, Application Data, or even based on properties (e.g. PublishLocationUrl) stored in an item its Application Data, then implementing a custom search indexing handler makes sense.

In this blog article I will guide you through the steps necessary to implement a handler, and build one that indexes a Component’s Expiry Date, set in the Component Metadata.

When implementing a search indexing handler three steps need to be covered:



  • The index field needs to be defined in the Sol-r Tridion Schema
  • The handler itself needs to be developed in a class that implements ISearchIndexingHandler.
  • This class needs to be configured in the searchIndexer section of Tridion.ContentManager.config



  • Field Schema Configuration


You can find the Sol-r Schema Field configuration here: Tridion\solr-home\tridion\conf\schema.xml, and for each field you need to add a field element to the fields section.

  <fields>

    <!-- ContentAuthoring.ExpiryDates -->
    <field name="ExpiryDate_dyn_s_dte" type="date" indexed="true" stored="true" />
    <!-- ContentAuthoring.Translation -->
    <field name="TranslationRequired_dyn_s_bln" type="boolean" indexed="true" stored="true" />
    <field name="ParentRevisionDate_dyn_s_dte" type="date" indexed="true" stored="true" />
    <!-- ContentAuthoring.PublishedTo -->
    <field name="PublishedToTargetIds_dyn_mvl_txt" type="string" indexed="true" stored="false" />
    <field name="PublishedToTargetTitles_dyn_mvl_txt" type="text" indexed="true" stored="false" />
  </fields>

In the example you see three fields defined. Expiry Date, and Published To Target Ids and Titles.

The name of the field needs to be set in a specific format to allow to parse the field value in the result set. E.g.: ExpiryDate_dyn_s_dte

Besides the name of the field, the name attribute contains information on whether or not the field is dynamic (_dyn), stored (_s) or multivalued (_mvl).

NB: Making a field stored will return it in the search result.

The last part of the field name must contain a type keyword:

Type
Type Code
Field Class Name
Boolean
bln
BooleanField
Date
dte
DateField
Double
dbl
DoubleField
Integer
int
IntegerField
Float
flt
FloatField
Long
lng
LongField
String
s
StringFIeld
TcmUri
uri
TcmUriField
Text
txt
TextField
Xml
xml
XmlField
 Table 1: Field definition

The dyn, mvl, s and [type] keywords are configured for Sol-r in the field element, and included in the name to support the SDL Tridion Search functionality.

To work with index fields you can use the helper classes shown in table 1 and defined in the Tridion.ContentManager.Search.Fields namespace.

NB: Although technically you’re not configuring a dynamic Sol-r field, the custom field is marked dynamic. Will revise this article later with some more details.

Keep in mind that when you define a string field the value is indexed in its entirety, and only text fields allow for partial and more advanced search matches.

Implementing the Search Indexing Handler

You can create your search indexing handler by developing a class that implements the ISearchIndexingHandler interface. This interface requires two methods:


public void Configure(SearchIndexingHandlerSettings settings)

public void ExtractIndexFields(IdentifiableObjectData subjectData, Item item)

The Configure method can be left empty, and ExtractIndexFields contains the actual implementation of your search index handler.

The following example indexes any ExpiryDate metadata field present in the Component:


public sealed class ExpiryDateSearchIndexingHandler: ISearchIndexingHandler

{
    const string ExpiryDateFieldName = "ExpiryDate";

    public void Configure(SearchIndexingHandlerSettings settings)
    {
    }

    public void ExtractIndexFields(IdentifiableObjectData subjectData, Item item)
    {
        var itemData = subjectData as VersionedItemData;

        if (SubjectHasMetadata(itemData))
        {
            AddExpiryDateToIndexItem(itemData, item);
        }
    }

    public void Dispose()
    {
    }

    static bool SubjectHasMetadata(RepositoryLocalObjectData itemData)
    {
        return itemData != null &&
                !string.IsNullOrEmpty(itemData.Metadata);
    }

    static void AddExpiryDateToIndexItem(RepositoryLocalObjectData itemData, Item item)
    {
        var expiryDate = GetExpiryDate(itemData.Metadata);

        if (expiryDate.HasValue)
        {
            item.Add(new DateField(ExpiryDateFieldName, expiryDate, true));
        }
    }

    static DateTime? GetExpiryDate(string xml)
    {
        var xe = XElement.Parse(xml);

        var expiryDateValue =
            xe.Descendants().Where(
                el => string.Equals(el.Name.LocalName, ExpiryDateFieldName, StringComparison.InvariantCultureIgnoreCase)).
                Select(el => el.Value).FirstOrDefault();

        DateTime expiryDate;
        return DateTime.TryParse(expiryDateValue, out expiryDate) ? expiryDate : default(DateTime?);
    }
}




Configuring a Search Indexing Handler



The third step is to configure your search indexing handler in the content manager configuration.

You can configure the handler in the searchIndexer section:

<searchIndexer heartbeatTimeout="300" indexingThreadCount="1" hostUrl="http://localhost:8983/tridion" hostUsername="[Domain]\[Account Name]" hostPassword="[Password]">
   <searchIndexingHandlers>
        <add type="Tridion.ContentManager.Search.Indexing.Handling.DefaultSearchIndexingHandler" assembly="Tridion.ContentManager.Search.Indexing, Version=6.1.0.996, Culture=neutral, PublicKeyToken=ddfc895746e5ee6b" />
        <add type="[Customer Name].ContentManager.Extensions.ContentAuthoring.ExpiryDates.ExpiryDateSearchIndexingHandler" assembly="[Customer Name].ContentManager.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9d4a72f7ed42e8d3, processorArchitecture=MSIL" />
    </searchIndexingHandlers>
</searchIndexer>

It might be possible that in your implementation these elements are encrypted. If this is the case you can encrypt the section again by resetting the host password using the SDL Tridion MMC Snap-in.

Expired Content Search Folders

When you create a Search Folder you need to query the entire name as specified in the Sol-r Schema.

The following example shows the Search Folder source to find the content that is expiring in the coming week:

<SearchFolder xmlns="http://www.tridion.com/ContentManager/5.1/SearchFolder">    <GeneralParameters>        <SearchQuery>ExpiryDate_dyn_s_dte:[NOW TO NOW+1WEEK]</SearchQuery>        <SearchIn xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="tcm:0-6-1" Recursive="true"></SearchIn>    </GeneralParameters>    <AdvancedParameters></AdvancedParameters></SearchFolder>

The following example shows the Search Folder source to find the Component published to two specific targets, Live (Public) and Live Secure:

<SearchFolder xmlns="http://www.tridion.com/ContentManager/5.1/SearchFolder">
    <GeneralParameters>
        <SearchQuery>PublishedToTargetTitles_dyn_mvl_txt:Live AND PublishedToTargetTitles_dyn_mvl_txt:"Live Secure"</SearchQuery>
        <SearchIn xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="tcm:0-4-1" Recursive="1"></SearchIn>
    </GeneralParameters>
    <AdvancedParameters></AdvancedParameters>
</SearchFolder>

Wrapping up and some additional advice


Now restart the COM+ application and search related services and call TcmReIndex.exe /all to reindex your entire search index.

To wrap up I would like to share some points I came across:


  • Updating Application data does not trigger updating the Search Catalog. You need to force this manually, which is not possible using the API, and the solution I found is unsupported.
  • You receive a Tridion.ContentManager.CoreService.Client.IdentifiableObjectData data object in the handler, and not the actual TOM.NET Tridion Item. This means that when you need to do anything with the item (e.g. retrieve Application Data) you need to use the Core Service.
  • When searching for these custom fields you need to use the full name as specified in the Sol-r Schema.
  • You can define multivalued index fields (nice to keep track of all publication targets a component is published to!), but they can’t be returned in the search result. Tridion doesn’t know how to handle these results, so set the stored attribute in the Schema configuration to false.

Good luck! I hope you find this article useful, and when you need any help, let me know.