Tuesday, 21 March 2017

Exploring the SDL Mobile Devices Database

It is possible to query the SDL Mobile Devices database by using a tool such as RazorSQL.

  • Download and start RazorSQL
  • Select Connections/Connect from the main menu
  • Click the Add Connection Profile tab
  • Select HSQLDB / HyperSQL as the database type as shown below.






























  • Click Continue
  • Set a Profile Name of your choice
  • Enter value "sa" as the Login
  • Leave the Password empty
  • Click the browse button to select the Database Script File
  • Select the sdl-context-repository script file as shown below.























Your new connection profile details should now appear as follows.


































Click the Connect button to access the database. You can start querying the devices database. Below are a few examples.

Select the device names per vendor showing the default width/height information













































Select the list of OS names













































Select the list of Browser names













































Conclusion

The ability to query the devices database helps to find out which values will be set in the ADF content store. This may assist the creation of conditional logic in your implementation as well as setting up Promotions in SDL Experience Optimization (SmartTarget).

Wednesday, 8 March 2017

Connecting to SDL Media Manager

The solution presented here assumes a Secure Token Service (STS) is readily available. The helper class below can be used for connecting to SDL Media Manager with a secure token.

using Migration.MediaManager;
using System.ServiceModel;
using System.IdentityModel.Tokens;
using System.Configuration;
using System.ServiceModel.Security;
using System.IdentityModel.Protocols.WSTrust;
namespace Migration.Helpers
{
public class MediaManagerHelper
{
private static SecurityToken RequestSecurityToken()
{
WSTrustChannelFactory factory = new WSTrustChannelFactory(new WS2007HttpBinding("ws2007HttpsBindingConfiguration"),
new EndpointAddress(ConfigurationManager.AppSettings["IssuerName"]))
{
TrustVersion = TrustVersion.WSTrust13
};
RequestSecurityToken rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference(ConfigurationManager.AppSettings["MediaManagerWebServiceAddress"]),
DelegateTo = new SecurityTokenElement(new UserNameSecurityToken(@"API\UploadToolUser", ""))
};
RequestSecurityTokenResponse resp;
return factory.CreateChannel().Issue(rst, out resp);
}
private static IMediaManager2011 GetClient(SecurityToken token)
{
var factory = new ChannelFactory<IMediaManager2011>("FederationEndpointHttps");
return factory.CreateChannelWithIssuedToken(token);
}
public static IMediaManager2011 GetMediaManagerClient()
{
SecurityToken token = RequestSecurityToken();
return GetClient(token);
}
}
}
Here is an extract from the App.config file containing the bindings and endpoint information.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="IssuerName" value="http://localhost:89/IWSTrust13" />
<add key="MediaManagerWebServiceAddress" value="https://TENANTNAME.sdlmedia.com/WebServices/MediaManager2011.svc" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
</appSettings>
<system.serviceModel>
<bindings>
<ws2007FederationHttpBinding>
<binding name="FederationEndpointHttps" transactionFlow="true" sendTimeout="00:05:00" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
<security mode="TransportWithMessageCredential">
<message>
<issuer address="http://localhost:89/IWSTrust13" binding="ws2007HttpBinding" bindingConfiguration="ws2007HttpsBindingConfiguration">
<identity>
<servicePrincipalName value="host/localhost" />
</identity>
</issuer>
<issuerMetadata address="http://localhost:89/IWSTrust13/?mex" />
<tokenRequestParameters>
<trust:SecondaryParameters xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<trust:KeyType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
<trust:KeySize xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">256</trust:KeySize>
<trust:Claims Dialect="http://schemas.xmlsoap.org/ws/2005/05/identity" xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsid:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" xmlns:wsid="http://schemas.xmlsoap.org/ws/2005/05/identity" />
<wsid:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" xmlns:wsid="http://schemas.xmlsoap.org/ws/2005/05/identity" />
</trust:Claims>
<trust:KeyWrapAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p</trust:KeyWrapAlgorithm>
<trust:EncryptWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptWith>
<trust:SignWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2000/09/xmldsig#hmac-sha1</trust:SignWith>
<trust:CanonicalizationAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/10/xml-exc-c14n#</trust:CanonicalizationAlgorithm>
<trust:EncryptionAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptionAlgorithm>
</trust:SecondaryParameters>
</tokenRequestParameters>
</message>
</security>
</binding>
</ws2007FederationHttpBinding>
<ws2007HttpBinding>
<binding name="ws2007HttpsBindingConfiguration" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
<readerQuotas maxStringContentLength="2147483646" maxArrayLength="2147483646" />
<security mode="Message">
<message establishSecurityContext="false" />
</security>
</binding>
</ws2007HttpBinding>
</bindings>
<client>
<!-- Adding the Media Manager endpoint -->
<endpoint address="https://TENANTNAME.sdlmedia.com/WebServices/MediaManager2011.svc" binding="ws2007FederationHttpBinding" bindingConfiguration="FederationEndpointHttps" contract="MediaManager.IMediaManager2011" name="FederationEndpointHttps" />
</client>
</system.serviceModel>
</configuration>
view raw App.config hosted with ❤ by GitHub
Note that TENANTNAME is the name of your company used by Media Manager.

Friday, 3 March 2017

Uploading images from SDL Web (Tridion) to SDL Media Manager

Consider the scenario where you need to migrate all the images from your SDL Web CMS to SDL Media Manager. For each image in SDL Web, the solution would involve:
  • Downloading the image
  • Uploading the image to SDL Media Manager
  • Creating a program and distribution in SDL Media Manager
  • Putting the distribution Live
  • Finding the "using components" in SDL Web and updating each one by replacing the relationship to the image in SDL Web with the relationship to the equivalent image in SDL Media Manager.
To complete all the steps above, you will require the following.
  • SDL Web Core Service client
  • SDL Web Stream Download client
  • SDL Media Manager client
  • ECL Service client
Here are the supporting app settings.

<appSettings>
<!-- Properties used by the Core Service -->
<add key="HostName" value="hostname-to-the-sdlweb-cme-server.com" />
<add key="ImpersonationUserDomain" value="the-domain"/>
<add key="ImpersonationUserName" value="the-username" />
<add key="ImpersonationUserPassword" value="********" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
<!-- Properties used by the Media Manager integration -->
<add key="IssuerName" value="http://localhost:89/IWSTrust13" />
<add key="MediaManagerWebServiceAddress" value="https://customerdomain.sdlmedia.com/WebServices/MediaManager2011.svc" />
<!-- Supporting properties for running the script -->
<add key="TridionToMediaManagerFolderMappingsFile" value="D:\migration\folder-structure-mappings-complete.xlsx" />
<add key="MigrationSupportDirectory" value="D:\migration\Images\" />
<add key="MigratingAssetSchemaTcmId" value="tcm:5-33-8" />
<add key="MediaManagerAssetType" value="Image" />
<add key="MediaManagerOutlet" value="Image Outlet" />
<add key="MediaManagerTags" value="Image" />
</appSettings>

As shown above, the first set of properties is related to the SDL Web (Tridion) Core Service. The second set of properties is related to Media Manager, which includes the IssuerName specifying the URL to the Secure Token Service (STS). The third set of properties is required for specifying:
  • A spreadsheet mapping the SDL Web (Tridion) folder structures to the equivalent folder structures in Media Manager.
  • A directory used for storing the images downloaded from SDL Web for uploading to Media Manager.
  • The tcmid of the schema (asset type) to be migrated. In the listing above, the tcm:5-33-8 corresponds to the Image multimedia schema.
  • The name of the asset type (as defined in Media Manager) to be migrated.
  • The named of the outlet (as defined in Media Manager) to be used when creating the distributions in Media Manager.
  • The name of the tag (as defined in Media Manager) to be used when uploading and creating the assets in Media Manager.
The Media Manager Secure Token Service is essential for running the asset migration. It allows the communication to Media Manager to be established through the API. In the application settings above, the STS service is available from the http://localhost:89/IWSTrust13/ URL.

The Media Manager API does not support the creation of folders. Therefore, before migration can take place, the folder structures in SDL Web must be replicated in Media Manager to accommodate the assets. The TridionToMediaManagerFolderMappingsFile configuration property specifies a spreadsheet containing the full mappings between the existing folder structures in SDL Web and the equivalent folders created in SDL Media Manager.

Downloading assets from SDL Web (Tridion)


The code for downloading assets from SDL Web is shown below.

private static Dictionary<string, string> DownloadFromTridion(StreamDownloadClient streamDownloadClient, ComponentData multimediaComponent)
{
Dictionary<string, string> downloadInfo = null;
// Prepare file for download in local file system
string originalFileName = Path.GetFileName(multimediaComponent.BinaryContent.Filename);
string downloadFile = Options.MigrationSupportDirectory + originalFileName;
FileStream fs = File.Create(downloadFile);
try
{
// Read metadata fields from Tridion
XDocument metadata = XDocument.Parse(multimediaComponent.Metadata);
var nss = metadata.Root.GetDefaultNamespace();
string altText = metadata.Root.Element(nss + "AltText") != null ? metadata.Root.Element(nss + "AltText").Value : String.Empty;
// Download asset from Tridion
byte[] binaryContent = null;
if (multimediaComponent.BinaryContent.FileSize != -1)
{
Stream tempStream = streamDownloadClient.DownloadBinaryContent(multimediaComponent.Id);
var memoryStream = new MemoryStream();
tempStream.CopyTo(memoryStream);
binaryContent = memoryStream.ToArray();
tempStream.Close();
}
if (binaryContent != null)
fs.Write(binaryContent, 0, binaryContent.Length);
downloadInfo = new Dictionary<string, string>();
downloadInfo.Add("file", downloadFile);
downloadInfo.Add("originalFileName", originalFileName);
downloadInfo.Add("altText", altText);
downloadInfo.Add("title", multimediaComponent.Title);
}
catch(Exception exception)
{
log.Error(exception);
}
finally
{
fs.Close();
}
return downloadInfo;
}

Uploading to SDL Media Manager


The code for uploading to SDL Media Manager is shown below. This code also creates the program, the distribution and puts the distribution live.

private static Dictionary<string, object> UploadToMediaManager(IMediaManager2011 mediaManagerClient,
Dictionary<string, string> downloadInfo, long mediaManagerFolderId, long assetTypeId,
long outletId, long[] tags)
{
// Upload to Media Manager
UploadInfoData uploadInfoData = new UploadInfoData
{
Author = "Migration Upload Tool",
MakeAvailableForWebPublishing = true,
ProgramCreation = ProgramCreationOptions.OneProgramPerItem,
DistributionCreation = DistributionCreationOptions.OneDistribution
};
string uploadUrl = mediaManagerClient.GetUploadUrl(mediaManagerFolderId, assetTypeId, tags, downloadInfo["title"], uploadInfoData);
using (WebClient client = new WebClient())
{
try
{
client.UploadFile(uploadUrl, downloadInfo["file"]);
}
catch(Exception exception)
{
log.Error(exception);
return null;
}
WebHeaderCollection webResponse = client.ResponseHeaders;
long assetId = 0;
long.TryParse(webResponse.GetValues("AssetId").First(), out assetId);
long programId = 0;
long.TryParse(webResponse.GetValues("ProgramId").First(), out programId);
long distributionId = 0;
long.TryParse(webResponse.GetValues("DistributionId").First(), out distributionId);
Dictionary<string, long> responseIdsUpload = new Dictionary<string, long> { { "assetId", assetId }, { "programId", programId }, { "distributionId", distributionId } };
// Update the asset's properties
AssetData assetData = (AssetData)mediaManagerClient.GetItem(ItemTypes.Asset, responseIdsUpload["assetId"]);
assetData.Name = downloadInfo["title"];
assetData.Description = !String.IsNullOrEmpty(downloadInfo["altText"]) ? downloadInfo["altText"] : downloadInfo["title"];
mediaManagerClient.UpdateItem(assetData);
// Update the program's properties
ProgramData programData = (ProgramData)mediaManagerClient.GetItem(ItemTypes.Program, responseIdsUpload["programId"]);
programData.Name = downloadInfo["title"];
programData.Description = !String.IsNullOrEmpty(downloadInfo["altText"]) ? downloadInfo["altText"] : downloadInfo["title"];
mediaManagerClient.UpdateItem(programData);
return CreateDistribution(mediaManagerClient, responseIdsUpload, downloadInfo["title"], mediaManagerFolderId, outletId);
}
}
private static Dictionary<string, object> CreateDistribution(IMediaManager2011 mediaManagerClient,
Dictionary<string, long> responseIdsUpload, string assetDescription,
long mediaManagerFolderId, long outletId)
{
// Create the Distribution
long programId = responseIdsUpload["programId"];
var newDistribution = new DistributionData
{
Name = assetDescription,
Description = assetDescription,
FolderId = mediaManagerFolderId,
IsLive = true,
OutletId = outletId,
ProgramIds = new[] { programId }
};
DistributionData distributionData = null;
try
{
distributionData = (DistributionData)mediaManagerClient.AddItem(newDistribution);
}
catch(Exception exception)
{
log.Error("Error creating the distribution in Media Manager");
log.Error(exception);
return null;
}
try
{
mediaManagerClient.PutLiveAsync(ItemTypes.Distribution, new long[] { distributionData.Id });
}
catch(Exception exception)
{
log.Error("Failed to put live distribution: " + distributionData.GlobalId);
log.Error(exception);
}
Dictionary<string, object> uploadInfo = new Dictionary<string, object>();
uploadInfo.Add("distributionId", distributionData.Id);
uploadInfo.Add("description", distributionData.Description);
uploadInfo.Add("globalId", distributionData.GlobalId);
return uploadInfo;
}

Updating the relationships in SDL Web (Tridion)


The download and upload steps complete the process of migrating assets from SDL Web to SDL Media Manager. However, it is also important to update the components using the migrated asset by swapping the reference for the new asset in SDL Media Manager.

This step relies on a SessionAwareEclServiceClient only available through a netTcpBinding which is not exposed. Therefore, a solution invoking this service can only run on the CME server itself.

<netTcpBinding>
<binding name="EclNetTcpBinding" maxReceivedMessageSize="2147483647" receiveTimeout="00:10:00" sendTimeout="00:10:00">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
</binding>
</netTcpBinding>
And through the following endpoint.

<client>
<endpoint name="EclBinaryEndpoint"
address="net.tcp://localhost:2660/ExternalContentLibrary/2012/netTcp"
binding="netTcpBinding"
bindingConfiguration="EclNetTcpBinding"
contract="Tridion.ExternalContentLibrary.Service.Client.ISessionAwareEclService"/>
</client>
You will need to copy the Tridion.ExternalContentLibrary.Service.Client.dll file from the SDL Web CME server and import into your solution in order to build the project.

The code below takes the core service and ECL clients, the migrated SDL Web multimedia component and a Dictionary object containing information gathered during the upload process. This information includes the distribution id, which is used to create or get the stub to the asset originating from Media Manager.

private static void UpdateWhereUsingComponents(ICoreService coreServiceClient, SessionAwareEclServiceClient eclClient, ComponentData componentData, Dictionary<string, object> uploadInfo)
{
Dictionary<string, string> stubInfo = null;
try
{
stubInfo = eclClient.CreateOrGetStubUris(new List<string> { "ecl:5-mm-" + uploadInfo["distributionId"] + "-dist-file" });
}
catch(Exception exception)
{
log.Error("Error generating stub for image " + componentData.Id);
log.Error(exception);
}
if (stubInfo == null || stubInfo.Count == 0)
return;
log.Info("Stub " + stubInfo.Values.First() + " generated for " + componentData.Id);
// Create a filter
UsingItemsFilterData usingItemsFilterData = new UsingItemsFilterData
{
BaseColumns = ListBaseColumns.Id, // to specify the detail in the XML
IncludedVersions = VersionCondition.OnlyLatestVersions,
ItemTypes = new[] { ItemType.Component } // to specify certain items
};
SynchronizeOptions syncOptions = new SynchronizeOptions();
syncOptions.SynchronizeFlags = SynchronizeFlags.FixNamespace | SynchronizeFlags.ApplyDefaultValuesForMissingMandatoryFields;
ReadOptions readOptions = new ReadOptions();
// Get the XML by calling .GetListXml on the client
XElement usingXML = coreServiceClient.GetListXml(componentData.Id, usingItemsFilterData);
XNamespace df = usingXML.Name.Namespace;
IEnumerable<XElement> itemElements = usingXML.Elements(df + "Item");
foreach (XElement itemElement in itemElements)
{
string usingItemTcmid = itemElement.FirstAttribute.Value;
string usingItemPubId = usingItemTcmid.Split('-')[0];
string multimediaLinkId = usingItemPubId + "-" + componentData.Id.Split('-')[1];
string stubTcmId = usingItemPubId + "-" + stubInfo.Values.First().Split('-')[1];
ComponentData usingItem = coreServiceClient.Read(usingItemTcmid, readOptions) as ComponentData;
usingItem = (ComponentData)coreServiceClient.SynchronizeWithSchema(usingItem, syncOptions).SynchronizedItem;
Boolean modified = false;
if (!String.IsNullOrEmpty(usingItem.Content) && usingItem.Content.Contains(multimediaLinkId))
{
usingItem.Content = usingItem.Content.Replace(multimediaLinkId, stubTcmId);
modified = true;
}
if (!String.IsNullOrEmpty(usingItem.Metadata) && usingItem.Metadata.Contains(multimediaLinkId))
{
usingItem.Metadata = usingItem.Metadata.Replace(multimediaLinkId, stubTcmId);
modified = true;
}
if (modified)
{
try
{
coreServiceClient.CheckOut(usingItem.Id, true, readOptions);
coreServiceClient.Save(usingItem, readOptions);
coreServiceClient.CheckIn(usingItem.Id, true, "Updated by the migration tool", readOptions);
log.Info("UPDATED RELATIONSHIP: " + usingItem.Id);
}
catch(Exception exception)
{
log.Error("Unable to update relationship in Tridion!");
log.Error(exception);
try
{
coreServiceClient.UndoCheckOut(usingItem.Id, true, readOptions);
}
catch(Exception undoCheckOutException)
{
log.Error("Unable to undo checkout!");
log.Error(undoCheckOutException);
}
continue;
}
}
}
}