Thursday, November 03, 2011

Pimping out the Tridion 2011 Service Pack 1

We're getting ready to launch Service Pack 1 in the coming weeks, so I thought it would be a good time to start pimping it out.

What's new with Service Pack 1
New & improved installer (with flashy images while installing :))
User Generated Content (aka Community Builder) is integrated into the platform
Translation Manager is also integrated into the platform, and now includes support for both World Server and Translation Management System

Content Manager Changes
Grouping of items in CM calls - You can now read multiple items from the database with a single TOM.NET or CoreService call, instead of having to retrieve them individually.
Description fields are now included in GetItems and GetListItems calls, not requiring you to load the full objects before reading this property.
New "Ancestors" calls: You can get the whole hierarchy of "parent" items with one simple call: GetAncestors or GetListAncestors


Content Delivery Changes
The Content Delivery layout is much simplified, so you don't have to pick-and-mix files from here and there. Instead there are 7 roles for Content Delivery that are pre-defined for you:
  • API
  • Cache Channel Service
  • Content Deployer
  • Monitoring
  • User Generated Content
  • HTTP Upload
  • Content Delivery Web Service
Just add water, and your soup is ready. Speaking of Content Delivery Web Service, there is also a native .NET oData Web Service that runs on top of IIS in this release.

There are a gazillion little changes more though, like showing breadcrumbs on items in Tridion, consistency of RTF behaviors across browsers, and quite a few fixes to other issues that have been fixed before.

Interested in knowing more? Check it out here.

Tuesday, November 01, 2011

SmartTargeting

Since the acquisition of Fredhopper and the launch of SmartTarget, I have been taking a keen interest on the development of the product, as I've always liked the concepts behind Fredhopper.

This puts me in a rather interesting position, as I have to interact very often with the technical teams when we discuss SmartTarget implementations, at any of the different stages - Pre-Sales, Proof of Concept or actual implementation.

And one thing that always strikes me as odd, is that technical teams just don't get it. Ask any developer about SmartTarget's targeting abilities and he will say "I could do it better". Without realizing that this is exactly why SmartTarget is so valuable to a marketing department: they don't need you to do it better.

Another thing that technical teams always ask for is the "generic", "magic button", "configuration based" personalize this page switch. Why would you want generic personalization? Actually, how can you do generic personalization? That's a contradiction in terms.

So, here's some examples of what you can do, out-of-the-box (meaning no custom code), with SDL SmartTarget:

  • Show a specific promotion if visitor comes from google.com and searched for term "holidays"
  • Show a specific promotion if day of the week is tuesday
  • Show a specific promotion if geo-location says you're coming from New York
  • Show a specific promotion if your profile says you have kids
  • Show a specific promotion if you're browsing the site with an iPad
  • Show a specific promotion if you're coming from "our-affiliate-site.com"

All of these are really easy to do with custom code too, any run-of-the-mill developer can do this.

What is really hard to do is:
  • Show a specific promotion if ALL OF THE ABOVE are true.
and
  • Let me change that 1st condition to be if visitor comes from Google OR Bing and the 3rd condition to be anywhere in the US except California
Which you can with out-of-the-box triggers provided in SmartTarget.

The possibility to target a specific individual is priceless in today's world, and Personalization is not about targeting "Large groups of individuals". We could do that in the past rather "easily" with Tridion Target Groups already.

And, of course, you can also use Predictive Targeting (or Personal Recommendations as our Fredhopper colleagues name it) to go that one step further and recommend a product based on YOUR visitors' past behaviors.

One of my latest stints discussing SmartTarget with a prospect was particularly enlightening in this respect. After being flooded with quite a few technical questions and remarks on how the system should automagically personalize everything, someone who had been quiet throughout most of the session asked:
"So, with this system I can tell it to show my promotions to <product X> only if it is Monday morning, the visitor has not purchased it before, the visitor has purchased a competing product in the same category, I have too much of <product X> in stock, push it to a live system, and change my mind 3 times about which promotion to show without ever asking IT for help?"

Yes, you can. Interesting times ahead of us.

Wednesday, October 19, 2011

You think you got what it takes?

My evolution in Tridion knowledge is a repeating (and repeatable) pattern:
  1. Looks cool, and pretty easy
  2. God what the hell is that?
  3. I hate you Tridion
  4. Why on earth is it working now?
  5. Hmm. That actually makes perfect sense
  6. WOW, look at all the shiny stuff I can do. Seriously, this is sooo coool!
Enjoy the glory for a few months, go back to square one.
    • It was like this with Infrastructure knowledge and deployer configuration and broker xml files and Tomcat and IIS 6.5 years ago.
    • It was like this with VBScript and Component Templates and XSLT and Metadata and Dynamic XSLT templates 6 years ago.
    • It was like this with c# and Event Systems with Interops and marshalling and unmarshalling of COM and multithreading TDSE objects 5.5 years ago (hey Robert, remember this one?)
    • It was like this with Compound Templating when it came out in February 2008.
    • It was like this with complex Taxonomies when it came out in mid 2009.

    So why would it be any different with the new Anguilla Framework introduced in 2011?

    I think I'm on the transition between steps 3 and 4, where I do get things to work but in many cases I am not really sure why. It has first and foremost to do with my lack of knowledge and any practical experience on _proper_ JavaScript development. Heck, other than simple carousels and show/hide stuff I don't think I had ever written more than 10 lines of JavaScript in one go.

    Here's a few tips on some of the things I've done so far too keep me somewhat sane, I hope this will help others:

    DISABLE CACHE ON YOUR BROWSER. Seriously, get the Firefox Web Developer extension, and turn the cache off while developing.

    Get used to a javascript console. I use Firebug mostly, and the messages it logs are priceless! Some people prefer Chrome, others IE. I don't really care. Just use one.

    Backtrack what the CME is doing when it fails. When your extension breaks Tridion completely (believe me, it will happen a LOT of times) Tridion will gracefully half-load and not show you any error. Luckily you had your Javascript console when the CME was loading, and you can clearly see what request broke it. Copy the url that it tried to load (it will look more or less like this: http://localhost/WebUI/Editors/CME/Views/Dashboard/Dashboard_v6.0.0.39607.6_.aspx?mode=js) and now you'll probably get a very useful ASP.NET error message instead (typically along the lines of "File X does not exist") which will point you in the right direction.

    And, most important, keep believing you CAN do it. You'll get there.

    Tridion Publisher and Custom Resolvers

    Back in the day, we used to disable Tridion's link resolving (aka "Link Propagation") at publish time by using the old and venerable Event System (for more details about link resolving, check this page).

    Recently I had to write some code to disable link resolving only under certain conditions for Tridion 2009, and started going down the true-and-tried road of Event System and "OnComponentPublishPre" events. Shortly after deployment in the test environment we started getting some really angry error messages from COM saying that my code was trying invalid casts.

    Obviously offended by the suggestion that my code wouldn't work, I tried to understand what the **** was going on, and eventually figured out that it was all down to this: MSXML cannot be used in .NET code. And since the Event System in pre-2011 days is still based on COM and the Publish Instruction is an MSXML document... it would never work.

    I could have re-written my Event System in VB6... but let's not even go down that path.

    After scratching my head for about half a second, and some nudging from @puf on the right direction, I decided to try writing a Custom Resolver for the Tridion Publisher.

    And I was so amazed with the results that I thought I should share it with you.

    There's quite a few differences between a Custom Resolver and the Event System approach, the main ones being that you write your code in native .NET (using TOM.NET objects) and that you do not completely disable link resolving - you just fiddle with the results instead.

    Another great advantage of a Custom Resolver is that it is forwards-compatible: the code you write for Tridion 5.3 or 2009 will also work with Tridion 2011(*).

    So, let's get to the code, shall we?

    Create a class library, add references to Tridion.Common, Tridion.ContentManager and Tridion.ContentManager.Publishing.

    Create a class that implements the Tridion.ContentManager.Publishing.Resolving.IResolver interface.

    Create a void method as follows:
    public void Resolve(IdentifiableObject item, ResolveInstruction instruction, PublishContext context, Tridion.Collections.ISet <ResolvedItem> resolvedItems)

    The resolvedItems collection is exactly what it looks like: this is the list of items that will be published unless we interfere with it. It's just a collection, so feel free to iterate through it, determine its type, remove items from it, etc. There's a reasonably good sample on the Tridion docs, just search for Custom Resolver.

    Just a few more words on the deployment:
    • The assembly you create must go into the GAC
    • You have to add your assembly to the Tridion.ContentManager.Config file in the "resolvers" section AFTER the default resolver from Tridion
    • Restart the publisher and you're in business

    (*) Hotfix CM_2011.0.1.74870 required in this pre-Service Pack 1 world.

    Sunday, October 16, 2011

    PowerTools 2011

    After some hard work from (mostly) Peter Kjaer, Chris Summers and Yoav Niran, the Power Tools project is finally under way.

    As from yesterday, there is a guide outlining the steps required to create a power tool, so I thought it is about time to start recruiting more cooperation from the community.

    The previous Tridion PowerTools were mostly a collection of ASP pages with some VBScript and Javascript helpers that provided a series of common functionalities:
    • Progress bars for long-running processes
    • Logging and debugging information
    • Item Pickers
    With the new Tridion 2011 UI and API (aka the Anguilla Framework) most of this functionality either broke or became obviously out-dated. Rather than taking the approach of making the power tools compatible with the new API (which would have been a monstrous task anyway) some people (Yoav and Chris mostly) had the guts to start this project from scratch.

    Though some of the common functionalities are still "undeveloped", there is enough in place to start playing. We do have now a common progress bar (pretty impressive btw), a nice and mostly clean WebService-based framework, a common set of javascript tools to handle the webservice responses, etc.

    The main missing point right now is an Item Picker, but there is some work happening on that one too.

    As it is clear if you read the No-nonsense guide to creating a Power Tool, there's a whole bunch of files to create per PowerTool, and each file has a very specific - and required - purpose. As the platform evolves we will be merging more and more of these features into a Common library, but for now you have to live with it...

    If you want to collaborate in this project - as we all hope you do - here's some of the things you may want to do:
    1. Join the powertools discussion 
    2. Join the IRC channel (yes! IRC! like in 1988) on freenode/#tridion
    3. Design the behavior of the next powertools. As it is now, this project is full of development geeks, but severely lacking on usability experts or actual powertool users. We could certainly use some help from a Functional point of view
    4. If you're dev-inclined, try out the guide mentioned above, and start playing with it. You may also want to write a better version of it, which we would ALL appreciate.
    5. Finally - determining icon sets for the tools would be a great contribution too.
    And hopefully someone will get around creating an installer for it...

    Sunday, September 25, 2011

    The art of knowledge sharing

    I've been recruited to help my daughter write a paper about Isotopes today. During our discussion about how the paper should or shouldn't be structured an idea bubbled through my mind regarding why some people do not share the knowledge they have - or perhaps just struggle to - while others insist on sharing it. As usual when you have a great idea at the wrong time, some sub-conscious thread of my brain kept on mulling on it, until it evolved into lots of analogies and anecdotal evidences that map directly to my life... So I thought I'd share it with you...

    Why don't you share knowledge?

    Here are some of the reasons I have used to avoid sharing knowledge:
    1. The concept that my knowledge equates to my value to an organization
    2. The concept that it takes me less time to do something than to teach it to others
    3. The idea that - god forbid! - someone else will claim to have reached a given conclusion without giving me proper credit.
    OK, number 3 can be a real reason to avoid sharing knowledge, but if you do work with someone that would do that to you, perhaps you should re-think the environment where you work in: join us.


    The impact of not sharing knowledge is probably hidden in plain sight for all those individuals that have worked in the same organization for more than 5 years just to find themselves still working in the same position they were hired for, with a very high reputation for what they do, and unable to progress in their careers. Fact is, they made themselves extremely valued at their line of work, and they can work miracles - being in fact so high above everyone else that works with them, that they are looked upon as almost demi-gods. But nobody really likes to work with them, and everyone else knows that these persons will do their job better when left alone.


    These are tell-tale signs of someone that for years accumulated knowledge and did not spread it throughout the organization - and now realizes that the reasons why nobody else can do his/her job is simply because he/she never took the time to explain how he/she does it. And therefore, nobody else can do it like he or she can, and therefore he/she must remain in his/her current position, because nobody else can do it.


    Sounds familiar?


    I am certainly oversimplifying many of the concepts involved in Knowledge Sharing, Knowledge Transfer and the much bigger discipline of Knowledge Management, but in my professional experience through life, knowledge sharing is rarely a result of using a given tool. Actually, I think the usage of a tool will only help if the knowledge sharing culture already exists.

    Why do you share knowledge?
     
    So, now that I have completely over-simplified why people will not share knowledge (knowing or unknowingly), here's some of the reasons why I think other people do share:
    1. Recognition - to be known as a guru, you have to - well - be known. Sharing your knowledge about a given topic, especially when it is something very specialized, will help establishing you as a point of reference about this topic
    2. Scalability of results - being able to reproduce the same quality of results while using different resources
    3. Being able to move up in the organization
    Reason #1 is usually the most visible reason, and also why some people criticize awards, like our MVP program - the fact that the person getting the award is only in it for himself. If that's the case, then I love it. Please go all be ambitious and parade your knowledge on Tridion so that I run out of my budget for MVPs. (I mean it).

    Reason #2 is the reason that logic and/or your management will ask from you. Professional organizations produce consistent, high-quality results. If only you are able to produce that same result, then your organization has failed.

    Reason #3 is not very apparent at first. Of course, you bask in the knowledge that you are the most successful executor of a given strategy, you positively glow when someone you just met mentions how your reputation precedes you, and you love the fact that people come out of their way to talk to you about something extremely technical. And then you go home, and realize you are doing exactly the same thing you did 5 years ago - except that you're even more arrogant about it now. And you're still being asked to do that same job, instead of being asked to create more people like you.


    Impact on Professional Services

    Professional Services organizations (excluding on purpose all the off-shore/body-shop organizations from this list) have learned a long time ago that their main asset is the most intangible of all: Knowledge. Working for the Professional Services branch of a Software company puts me often in a position where Knowledge Sharing is the _only_ way forward. My job is to assist you in unlocking the potential of the tool you bought, not to show you how good I am.

    If you don't work for the Software Vendor you may be very interested in maintaining the knowledge within your own organization (rather than the client that hired you), but as the software vendor I want you to learn it. The least services you need from us in the future - while happy and supportive of the tool - the more successful I have been. And our team can focus on what it's usually best at: introducing new customers to the amazing flexibility of the tool they chose to buy.

    Promoting Knowledge Sharing

    So, with all these things in mind - which pretty much sums up what my brain went through in the discussion I had with my daughter - how do you make sure you're making yourself redundant?

    If I knew the exact way to do this, I'd be a billionaire. So here's some things that I think will work.
    Promote knowledge sharing by rewarding the attitude.
    Remember reason #1 why people share knowledge? Promote it. Give the person a public recognition of their great job at knowledge sharing. It does not need to be monetary, and it certainly should not be a contest (no "employee of the month" please). Anything as simple as nod to that person (or group of persons) in the company's internal newsletter will do miracles to that person's willingness to share knowledge.
    Give that person additional responsibilities within the realm of sharing knowledge.
    Assign a junior resource to tag along with the person, not "to learn and become like you", but to "help you move on by taking care of the more mundane aspects of your job". If you read it attentively, you will see that both sentences carry the exact same result: someone will learn by tagging along, but the second sentence puts more emphasis on the benefit to the person with the highest knowledge, which may also make them more pre-disposed to assist in a mutually beneficial way.

    If both conditions are in place, you will naturally progress in your organizations - sometimes without even noticing it.

    If you don't do it, nobody will do it for you: start sharing knowledge now, or risk doing the same job forever.

    Friday, September 23, 2011

    Recursing through Group Members with the Core Service

    Yesterday I had to write a simple method to get me a list of all users who are members of a given group, using the Core Service.

    Obviously, this is one of those tasks you'll immediately classify as "simple" or "easy" or "low complexity". Which is probably true, if you happen to have done this before...

    Only one added twist, the list should contain all users who are members of this group, including sub-groups (groups members of the same group).

    In TOM.NET this is a trivial foreach(Trustee trustee in group.Members), but in CoreService-land (and WCF-land) we don't have the Object Model available, only the data model. So I had to twist my mind for a while to get this to work, but eventually got it working as follows.

    public Dictionary<string, string> GetUserEmailsByName(GroupData group)
    {
        Dictionary<string, string> result = new Dictionary<string, string>();
        GroupMembersFilterData groupMembersFilter = new GroupMembersFilterData();
        foreach (XmlNode groupMemberNode in _coreServiceClient.GetListXml(group.Id, groupMembersFilter))
        {
            TrusteeData trustee = (TrusteeData)_coreServiceClient.Read(groupMemberNode.Attributes["href", Constants.XlinkNamespace].Value, ReadOptions);
            if (trustee is UserData)
            {
                if (trustee.Description.Contains("@"))
                {
                    string userDescription = trustee.Description;
                    string userEmail = UserEmailRegex.Match(userDescription).ToString();
                    userDescription = userDescription.Replace(userEmail, "").TrimEnd();
                    userEmail = userEmail.Replace("(", "").Replace(")", "");
                    if (!result.ContainsKey(userDescription))
                        result.Add(userDescription, userEmail);
                }
            }
            else if (trustee is GroupData)
            {
                Dictionary<string, string> subMembers = GetUserEmailsByName((GroupData)trustee);
                foreach (string key in subMembers.Keys)
                {
                    if (!result.ContainsKey(key))
                        result.Add(key, subMembers[key]);
                }
            }
        }
        return result;
    }
    

    The hard part to figure out was how to get the list of "Members" for this group, since GroupData does not expose that information:

    _coreServiceClient.GetListXml(group.Id, groupMembersFilter)

    Adding one more to the bag of CoreService patterns...

    Saturday, August 20, 2011

    Using Dreamweaver Field Notation in Tridion C# code

    If you've done any Tridion Dreamweaver Templates you have surely come to appreciate the DW notation syntax that Tridion introduced with version 5.3. Compared to what we had to do before to read a field, this syntax was much easier - both on the eye and on the sanity.

    If you have no clue what I'm talking about, here's an example of the difference between old-school (pre-2008) template code to read one field from a component, and the new way:

    Old School
    [%= Component.Fields("summary").value(1)%]
    Dreamweaver Notation
    @@summary@@

    Not very different you say? What about reading a value from an embedded field then?
    Old School
    [%=Component.Fields("paragraph").value(1).Fields("embeddedsummary").value(1)%]
    Dreamweaver Notation
    @@paragraph.embeddedsummary@@


    OK, so you agree with me now? If you don't agree yet, then try comparing the code when looping through multivalue collections...


    Another great feature of Compound Templating (perhaps the best feature some will say) was the introduction of Template Mediators. This allows us to (out of the box) use .NET-based languages as Template Building Blocks, both using .NET assemblies or c# Fragments. Some of you may even use the Community owned (mostly written and maintained by Yoav Niran) XSLT Mediator that lets you use XSLT Building Blocks in Compound Templates.

    So, plenty of good stuff. One thing that grandly p****d me off with the c# implementation was that it was still as complex - if not more - to read a component's field value than it was before with VBScript.

    Here's how you would read the same Embedded Summary field using c#:
    Component c = (Component)engine.GetObject(package.GetByName(Package.ComponentName));
    ItemFields fields = new ItemFields(c.Content, c.Schema);
    ItemField embeddedField = (ItemFields)fields["paragraph"];;
    TextField textField = embeddedField["embeddedsummary"];
    String textFieldValue = textField.Value;

    Not really user friendly, is it? Another thing you may notice is that the field Type is also needed when reading a field, so what happens if the field type changes from, say, SingleLineTextField to KeywordField? Yup, your code will not work anymore and needs to be changed (there's a lot to say about Schema changes, but that's for another day).

    So, why can't we use a DW-like notation in c#? Well, the short answer is that you can't because Tridion didn't provide it for you. The longer answer is that if you're one of the near 1000 people that downloaded the Dreamweaver Get Extension from Tridion World, you can, and here's how.

    1. Reference Tridion.ContentManager.Extensions.Templating.dll from your Visual Studio Project
    2. Add "using Tridion.ContentManager.Extensions;" to your .cs
    3. Start using the "FieldOutputHandler" class
    Here's an example to get you started:
    FieldOutputHandler h = new FieldOutputHandler(page.Id, engine, package);
    meta.Add("Title", h.GetStringValue("Metadata.SEO.SEOTitle"));
    meta.Add("Keywords", h.GetStringValue("Metadata.SEO.SEOKeywords"));
    meta.Add("Description", h.GetStringValue("Metadata.SEO.SEODescription"));

    This class has a lot of parameters, configuration settings, SiteEdit-related instructions, and also can be used for a lot more than just outputting string values...

    For one, it can read _any_ field as a String, so you don't need to worry about the field's type.
    Second, it can - just like the Get Extension - read fields from other objects, like Keyword Metadata, publication Metadata, etc, etc.
    Third, you can drill down into a linked component's field value, which could also be a linked component, etc, etc.

    Here's another example where this handler is being used to loop through values of a configuration component:
    for (int x = 0; x < TotalConfigs; x++)
    {
        FieldOutputHandler h = new FieldOutputHandler(PubMeta.GetComponentLinkField("Metadata.Configuration").Values[x].Id, engine, package);
    
        int varCount = h.GetEmbeddedSchemaField("Fields.Keys").Values.Count;
        for (int i = 0; i < varCount ; i++)
        {
            String name = h.GetStringValue(String.Format("Fields.Keys[{0}].Key", i));
            String value = h.GetStringValue(String.Format("Fields.Keys[{0}].Value", i));
            if (package.GetByName(name) == null)
            {
                package.PushItem(name, package.CreateStringItem(ContentType.Text, value));
            }
        }
    }

    Last but not least, I know the class name sucks. I was quite uninspired that day. Have fun with your newly-discovered Tridion powers!

    Tuesday, August 16, 2011

    SDL Tridion Jobs, Part II

    Back in 2008 I wrote a post about the growing number of job offers for Tridion in the US. Back in those days, about 18 months after we had opened our office in New York, I was surprised to see that we had as many as 7 active job offers on the Internet for Tridion experts in the US. Before I crossed the pond from Amsterdam I was probably getting an average of a job offer per week, and it (understandingly) took a while for that to pick up in the US since we had absolutely no market share back then.

    Boy, have we come a long way...

    About 50% of tweets mentioning "tridion" are job offers. Monster.com lists 17 job openings for Tridion developers. Dice lists 14. The Washington Post lists 89. Simply hired 125.

    Now play close attention to this graph comparing relative job growth between us and the 2 main competitors we're usually competing against.

    Oh yeah, we have come a long way. Let me know if you're searching for a job in a growing software area...

    Thursday, July 28, 2011

    Manually configuring a Tridion 2011 .NET Content Deployer Instance

    I'm a big fan of doing "manual" installations of the Tridion Content Delivery layer (be it for .NET or Java, as is probably clear here) and I had not got around for a while to do a complete .NET setup so I could document it somehow.

    Until today.

    Please note: the steps below are for a 64-bit server. On a 32 bit server you need a couple more files, copied from the x86 installation folder.

    These are the steps taken:
    Get your files right:
    The Hotfix rollup #1 shipped with quite a few updated jar files, but somehow not all files are shipped (cd_tcdl, cd_dynamic and probably a few others are missing, and you need the originals from the 2011 installer). Solution: copy the updated jar files from the hotfix rollup _over_ the files shipped with 2011.

    Create your HTTP deployer website:
    This is the basic structure for the site:
    /
    /bin
    /bin/config
    /bin/lib
    In the root, ( / ) drop the httpupload.aspx
    In /bin, drop the following 4 dlls:
    • netrtsn.dll
    • Tridion.ContentDelivery.Configuration.dll
    • Tridion.ContentDelivery.dll
    • xmogrt.dll
    in /bin/config, place the following configuration files (more details on what goes in these files later):
    • cd_deployer_conf.xml
    • cd_storage_conf.xml
    • logback.xml
    in /bin/lib, drop a s***load of jar files...
    All jar files from [ContentDeliveryInstall]/java/third-party-lib
    The following files from [ContentDeliveryInstall]/java/lib:
    • cd_broker.jar
    • cd_cache.jar
    • cd_core.jar
    • cd_datalayer.jar
    • cd_deployer.jar
    • cd_linking.jar
    • cd_model.jar
    • cd_tcdl.jar
    • cd_upload.jar
    And, last but certainly not least, add your jdbc (sqljdbc or ojdbc) driver to this folder.

    Configuration

    Obviously you must change the settings in these files to match your server configuration (sorry if it doesn't display very well on this blog, copy/pasting should work).

    cd_deployer_conf.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- The Tridion Content Distributor Deployer configuration specifies all
        configuration values required to receive and deploy content to a delivery system. -->
    <Deployer Version="6.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:noNamespaceSchemaLocation="schemas/cd_deployer_conf.xsd">
        <Processors>
            <Processor Action="Deploy" 
       Class="com.tridion.deployer.Processor">
                <Module Type="SchemaDeploy" 
        Class="com.tridion.deployer.modules.SchemaDeploy"/>
                <Module Type="PageDeploy" 
        Class="com.tridion.deployer.modules.PageDeploy">
                     <Transformer Class="com.tridion.deployer.TCDLTransformer"/>
                </Module>
                <Module Type="BinaryDeploy" 
        Class="com.tridion.deployer.modules.BinaryDeploy"/>
                <Module Type="ComponentDeploy" 
        Class="com.tridion.deployer.modules.ComponentDeploy"/>
                <Module Type="TemplateDeploy" 
        Class="com.tridion.deployer.modules.TemplateDeploy"/>
                <Module Type="PublicationDeploy" 
        Class="com.tridion.deployer.modules.PublicationDeploy"/>
                <Module Type="TaxonomyDeploy" 
        Class="com.tridion.deployer.modules.TaxonomyDeploy"/>
    
                <Module Type="ComponentPresentationDeploy"
        Class="com.tridion.deployer.modules.ComponentPresentationDeploy">
                    <Transformer Class="com.tridion.deployer.TCDLTransformer"/>
                </Module>
                <Module Type="StructureGroupDeploy" 
        Class="com.tridion.deployer.modules.StructureGroupDeploy"/>
            </Processor>
            <Processor Action="Undeploy" 
       Class="com.tridion.deployer.Processor">
                <Module Type="PageUndeploy" 
        Class="com.tridion.deployer.modules.PageUndeploy"/>
                <Module Type="ComponentPresentationUndeploy" 
        Class="com.tridion.deployer.modules.ComponentPresentationUndeploy"/>
                <Module Type="TaxonomyUndeploy" 
        Class="com.tridion.deployer.modules.TaxonomyUndeploy"/>
                <Module Type="StructureGroupUndeploy" 
        Class="com.tridion.deployer.modules.StructureGroupUndeploy"/>
            </Processor>
        </Processors>
        <Queue>
            <Location Path="c:\tridion\incoming" 
       WindowSize="20" Workers="10" Cleanup="true" Interval="2s"/>
        </Queue>
        <HTTPSReceiver MaxSize="10000000" 
      Location="C:\tridion\incoming" InProcessDeploy="true"/>
         <License Location="c:/Program Files(x86)/Tridion/config/cd_licenses.xml"/>
        <TCDLEngine>
            <Properties>
                <!-- Default code generation will always be targetted towards the use of tag libraries and
                      server controls. Uncomment these lines to drop that behaviour -->
                <Property Name="tcdl.dotnet.style" Value="controls"/>
                <Property Name="tcdl.jsp.style" Value="tags"/>
            </Properties>
        </TCDLEngine>
    
    </Deployer>
    
    
    cd_storage_conf.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration Version="6.0"
             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>
          <Storage Type="persistence" Id="sqlserver" 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="localhost" />
              <Property Name="portNumber" Value="1433" />
              <Property Name="databaseName" Value="Tridion_Broker" />
              <Property Name="user" Value="TridionBrokerUser" />
              <Property Name="password" Value="tridion" />
            </DataSource>
          </Storage>
          <Storage Type="filesystem" Class="com.tridion.storage.filesystem.FSDAOFactory" Id="iisFile">
            <Root Path="c:\inetpub\wwwroot" />
          </Storage>
        </Storages>
      </Global>
      <ItemTypes defaultStorageId="sqlserver" cached="false">
        <Item typeMapping="Page" cached="false" storageId="iisFile"/>
        <Item typeMapping="Binary" cached="false" storageId="iisFile"/>
      </ItemTypes>
      <License Location="c:/Program Files (x86)/Tridion/config/cd_licenses.xml"/>
    
    </Configuration>
    
    logback.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true">
        <!-- Properties -->
        <property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
        <property name="log.history" value="7"/>
        <property name="log.folder" value="c:/tridion/log/website"/>
        <property name="log.level" value="debug"/>
    
        <!-- Appenders -->
        <appender name="rollingCoreLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${log.folder}/cd_core.%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>${log.history}</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>${log.pattern}</pattern>
            </encoder>
            <prudent>true</prudent>
        </appender>
    
        <!-- Loggers -->
        <logger name="com.tridion" level="${log.level}"/>
        <root level="OFF">
            <appender-ref ref="rollingCoreLog"/>
        </root>
    </configuration>

    Now you have to create a website in IIS that uses this folder as its root folder. If using 2008 R2 you may want to change the application pool identity to Network Service, and you will have to give this user WRITE permissions on the filesystem storage location, as specified in cd_storage_conf.

    And that's pretty much it. Configure a Publication Target that uses your new website:/httpupload.aspx and you're ready to go.

    Last tip: If you need to publish large packages (think > 4 MB or whatever is the low limit for an IIS POST by default) create a web.config for this website and put the following inside:

    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="false" targetFramework="4.0" />
        <httpRuntime maxRequestLength="2097151"
        executionTimeout="600"/>
      </system.web>
      <system.webServer>
        <security>
          <requestFiltering>
            <requestLimits maxAllowedContentLength="2000000000" />
          </requestFiltering>
        </security>
      </system.webServer>
    </configuration>
    

    Thursday, July 21, 2011

    Spicing the community up

    As the most visible face of the SDL Tridion MVP Selection Panel I get asked plenty of questions about the selection process, the nominations, the qualifications of the MVPs, etc.

    Here's an FAQ to try to sort this thing once and for all:

    How do I get to be an MVP?
    Very simple:
    1. Contribute to the community. This includes - but is not limited to - twitter, facebook, linkedin, personal blogs, the Tridion Forum, SDLTridionWorld
    2. Get nominated by following the instructions on the MVP Program page
    Can I nominate myself?
    Yes.


    What is a Community Builder?
    An MVP that works for SDL.

    Who are the Community Builders?
    This information is not publicized since we want to promote external MVPs - partners, customers, independents. However, we felt that some contributions from our own people were too good to go unpunished so SDL employees receive a Community Builder award instead. Letting the wider world know about "person X" being a Community Builder is left to the person's own discretion.

    Why is {person A} an MVP and not {person B}?
    This is one of the most common questions I get. Well, likely because {person B} did not follow the steps outlined above on "How do you get to be an MVP?". The MVP program's goal is NOT to identify the best Tridion resources out there. In many cases, the current MVPs would not even qualify to a "Top 20" of the best Tridion resources in the world (in my not-so-humble opinion). They get the award not because of what they know (though that helps) but because of what they do. Every time an MVP answers a question in the Forum, hundreds of people (potentially) learn from it. Every time an MVP blogs something, the whole world can learn from it. Every time {person B} does a very successful implementation and does not tell the world anything about the solutions chosen, we all lose.

    Are you saying MVPs are not the best of the best?
    Yes, I am saying that being an MVP does not mean you are better than anyone else. It means you share more than others.


    Now that this little FAQ is out of the way...
    What the **** are our current MVPs doing? It sure has been a long time since I read anything good by any of them (exception made to Walter that keeps on pushing good stuff out).

    Sunday, June 05, 2011

    Importing Content into Tridion

    Something I get asked almost every time a new project starts - or a new consultant or partner starts working with Tridion - is how to import content into Tridion.

    It kinda baffles me, since I always thought it is pretty easy to import content into Tridion, but apparently that's not the case. Here's a few things to consider, I'm pretty sure most of this applies to _any_ content management system, and is not really related to Tridion.

    Well formed content
    Though it seems obvious, I see many content migrations coming from systems that do not enforce a strict XML schema as Tridion does, and therefore a simple one-to-one migration will fail miserably. "Easy" workarounds on this one:
    - Use XmlWriter when creating the content representation for Tridion, and the ItemFields collections to create your content in. If it fails validation, it will probably fail before you try to save it.
    - When dealing with Rich Text fields, use Tidy.NET to ensure your content is valid Xhtml.

    Consider if a content migration is really what you want
    One of the main reasons to change WCM is that your current content format does not match the business requirements. Guess what happens if you migrate your content "as-is" into Tridion? Yup, the content format still does not match your business requirements. So why are you even contemplating migration? Sure, you can get some of the content in, but you really should think about what you're trying to achieve before spending weeks writing a content migration tool that will prove to be worthless in a very short time frame. Do not underestimate the power of manual content migration in some cases.

    How easy is it to get the source content?
    This obviously depends on a lot of aspects of your current/old WCM, not all of them are as easy, and all of them are different.

    In other words, really think about what it is you're trying to achieve before embarking on a migration project that insists on changing mid-way through the migration.

    Since you read this far, here's a couple of bonus code samples :)

    Converting html to xhtml using Tidy.NET:
    private const String XhtmlNamespace = "http://www.w3.org/1999/xhtml";
    public static String ConvertHtmlToXhtml(String source)
    {
        MemoryStream input = new MemoryStream(Encoding.UTF8.GetBytes(source));
        MemoryStream output = new MemoryStream();
    
        TidyMessageCollection tmc = new TidyMessageCollection();
        Tidy tidy = new Tidy();
    
    
        tidy.Options.DocType = DocType.Omit;
        tidy.Options.DropFontTags = true;
        tidy.Options.LogicalEmphasis = true;
        tidy.Options.Xhtml = true;
        tidy.Options.XmlOut = true;
        tidy.Options.MakeClean = true;
        tidy.Options.TidyMark = false;
        tidy.Options.NumEntities = true;
    
        tidy.Parse(input, output, tmc);
    
        XmlDocument x = new XmlDocument();
        XmlDocument xhtml = new XmlDocument();
        xhtml.LoadXml("<body />");
        XmlNode xhtmlBody = xhtml.SelectSingleNode("/body");
    
        x.LoadXml(Encoding.UTF8.GetString(output.ToArray()));
        XmlAttribute ns = x.CreateAttribute("xmlns");
        ns.Value = XhtmlNamespace;
        XmlNode body = x.SelectSingleNode("/html/body");
        if (body != null)
            foreach (XmlNode node in body.ChildNodes)
            {
                if (node.NodeType == XmlNodeType.Element)
                    if (node.Attributes != null) 
                        node.Attributes.Append(ns);
    
                if (xhtmlBody != null) 
                    xhtmlBody.AppendChild(xhtml.ImportNode
                        (node, true));
            }
        return xhtmlBody != null ? xhtmlBody.InnerXml : null;
    }
    
    

    Getting a new or existing component (for update vs creation, CoreService with a custom client library)
    static Component GetNewOrExistingComponent
        (string componentName, Folder folder)
    {
        Component returnObject = null;
        componentName = SecurityElement.Escape(componentName);
        XmlNamespaceManager nm = new XmlNamespaceManager
            (new NameTable());
        nm.AddNamespace(Constants.TcmPrefix,
            Constants.TcmNamespace);
        CoreServiceSession session =
            new CoreServiceSession(CoreServiceEndpoint);
        OrganizationalItemItemsFilter filter = 
            new OrganizationalItemItemsFilter(session)
                {ItemTypes = new[] {ItemType.Component}};
    
        string xpath = String.Format
            ("tcm:Item[@Title='{0}']", componentName);
        XmlElement listItems = folder.GetListItems(filter);
        if (listItems != null)
            if (listItems.SelectNodes(xpath, nm).Count > 0)
            {
                string componentId = listItems.SelectSingleNode
                    (xpath, nm).Attributes["ID"].Value;
                returnObject = session.GetObject
                    (new TcmUri(componentId)) as Component;
            }
            else
            {
                returnObject = new Component(session, folder.Id);
            }
        return returnObject;
    }
    

    Note: This sample uses a custom client Library I wrote on top of the CoreService, and this library is not available yet - and I'm not sure I will make it available at all due to how long it took me to write it, and the fact that it is a work-in-progress. Releasing it means supporting it, and I unfortunately don't have the time to support my plants at home, let alone a still-half-buggy library that someone may try to use in production systems. Anyway, the code just looks like TOM.NET, so you shouldn't have any trouble reverse-engineering what this code does.

    Sunday, March 27, 2011

    Rants on Tridion implementations

    Those of you who know me, and work/worked with me, know that my job is not always an easy or necessarily happy one. In a nutshell, I tend to tell customers that "if you see me walk into your project, it means you're in trouble".

    So, if you're about to start implementing Tridion, and you don't want to see me walk into your project to fix it, here's a few things to remember:

    Before you try to do XYZ with Tridion, try to do it without Tridion. Seriously, if you don't know what the expected output of a page is, how do you expect to write a template that "does" it?

    If you think you know WCM and therefore learning Tridion is a non-issue, think again. Then re-think. And a year later re-evaluate what you know about WCM. If there is one thing I can tell about Tridion and the other WCM packages available, is that Tridion IS different - and customers buy the software for those EXACT reasons that make it different. If you're planning to implement Tridion just like you would [insert random WCM package here], then why don't you go implement [random WCM package] instead? Many times I've heard things like "We're not planning to use BluePrinting", "why can't I just create pages" and "feature XYZ should be out of the box" (this last one is my favourite, since everyone wants XYZ done differently, but out of the box anyway). The higher-up people that made the decision to buy Tridion were likely triggered to do it based on Blueprinting, Component-based content and the extensibility points offered - not because they thought the implementation team likes those concepts.

    Stop being a language zealot. So what if you need to understand Java for deployer extensions? And so what if you need to use c# for Event Systems? It's not as if programmers care about that? You should care functionality and complete API, not what language. If language _really_ is an issue for you, then write your own mediator, there's even a sample for that in the documentation.

    Make sure you understand that you will always need 2 types of people in your development team: Web designers and programmers. Web designers will not do a good job writing templates, and programmers are typically bad web designers. It is one of the best features of the product that both teams can work together by focusing on different template building blocks. So use it.

    Think before you do anything. This should be a no-brainer, right? Some examples...
    Everyone wants to implement SmartTarget, but it is OH-SO-RARE that people actually know what they want to do with it. Developers don't get it, they think they could have done everything themselves through code (missing the point that this is exactly what SmartTarget is about).
    Audience Segmentation? You need to know the _current_ audience before you start segmenting it.

    And I could go on ranting, but enough negativity for today. Here's how I tell people to learn Tridion.
    1. Get yourself a development environment to play with.
    2. Find your way into the Tridion Developer Forum (via Customer Support).
    3. Get yourself enrolled into a Technical Training. Really. It will open your eyes and save you a lot of time.
    4. Read the template implementation guide. I am not kidding, RTFM can do miracles for you.
    5. Now implement a simple, one-page site. Make sure all content is translatable via blueprinting, and editable via SiteEdit.
    6. Once that's working, implement the multiple language versions of that site.
    I tend to tell people to start with something really simple (crawling before walking). Like the Google home page, for instance.

    OK, enough ranting for the day, happy thoughts and enjoy implementing Tridion. It is an amazing piece of software, and once you get it, you will think differently about WCM systems.

    Sunday, February 13, 2011

    Tutorials and Copyright...

    I'm having a bit of a conundrum here. I wrote a few quite extensive Tutorials on Tridion 2011 which I want to make public via Tridion World but I can't understand if I'm infringing copyright or not.

    Let me explain: In these tutorials there are very detailed, step-by-step instructions with loads of screenshots. My goal here is to make sure I don't ever again get basic questions like "what do I need to install for Tridion to use an Oracle database back end?" or "What are the Windows 2008 R2 Roles I must install?".

    Of course, having Tridion screenshots is NOT copyright infringement, because I work for the copyright owner and I would be distributing it via the copyright's owner website. I am allowed to create those screenshots and empowered to distribute them via Tridion World (but I am not allowed to distribute it via my own website, even though many other people do that and we don't seem to care much about it).

    The way I see it, I can't really distribute the Oracle Client installation screenshots, or Windows 2008 install, or the SQL Server 2008 R2 installation screenshots.
    And if I remove those screenshots, the tutorials are not as detailed as I need them to be.

    Anyone out there with suggestions? Should I contact the 3rd parties (Oracle and Microsoft mostly) and ask for their approvals?

    I'll figure it out...

    Update: found some info on Screenshot copyright in general and Microsoft guidelines for screenshots, and it looks like I'm in the clear, since I do not modify any of the screenshot content, do clearly identify it as being from an Oracle or Microsoft product and don't do anything evil with it.

    So, new tutorials to show up soon in Tridion World