Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all 525 articles
Browse latest View live

AEM 6530 - Assets Sample Form in Content Fragments

$
0
0

Goal


Add a new side panel tab and form in Content Fragments for additional data

Demo | Package Install | Github


Form



Data in CRX



Solution


1) Login to CRXDe and create the form /apps/eaem-content-fragment-form/cf-form



2) Set the attributes for form data path in /apps/eaem-content-fragment-form/editor/contentfinder.jsp

<%@ page import="org.slf4j.Logger" %>
<%@ page import="org.slf4j.LoggerFactory" %>
<%@page session="false" contentType="text/html; charset=utf-8" %>
<%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.0" %>
<%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %>
<cq:defineObjects/>

<%
final Logger LOG = LoggerFactory.getLogger(this.getClass());

final String FORM_DATA = "/jcr:content/metadata/eaemFormData";

String[] content = new String[]{slingRequest.getRequestPathInfo().getSuffix() + FORM_DATA};

try{
request.setAttribute("aem.assets.ui.properties.content", content);

if (content.length == 1) {
request.setAttribute("granite.ui.form.contentpath", content[0]);
}
}catch(Exception e){
LOG.error("Error creating form playable media content", e);
}
%>


3) Create clientlib /apps/eaem-content-fragment-form/clientlib, set the categories to dam.cfm.adminpage.v2 and dependencies to lodash

4) Add /apps/eaem-content-fragment-form/clientlib/js.txt with the following content

                         cf-form.js

5) Add /apps/eaem-content-fragment-form/clientlib/cf-form.js with the following code

(function($, $document) {
var EAEM_FORM_PAGE = "/apps/eaem-content-fragment-form/cf-form.html",
formTabAdded = false, initialData;

$document.on("foundation-contentloaded", addFragmentFormTab);

function addFormActions(){
$(".button-apply").on("click", function(e) {
$("form").submit();
document.location.href = $("#Editor").data("return");
});

$(".button-cancel").on("click", function(e) {
Dam.CFM.editor.Core.cancel();
});
}

function addFragmentFormTab() {
if (formTabAdded) {
return;
}

formTabAdded = true;

var $sidePanel = $("#SidePanel"),
$panelTabs = $sidePanel.find("coral-tabview");

if (_.isEmpty($panelTabs)) {
return;
}

var tabList = $panelTabs[0].tabList;

var mediaTab = tabList.items.add({
title: "Experience AEM Form",
label: {
innerHTML: '<coral-icon icon="film" size="S"/>'
}
});

var panelStack = $panelTabs[0].panelStack;

panelStack.items.add({
content: {
innerHTML: getFragmentFormTabContent()
}
});

mediaTab.on('click', function(){
openEditorPage(EAEM_FORM_PAGE);
});

addFormActions();

saveInitialState();

addTabNavigationAlert();

workarounds();
}

function addTabNavigationAlert(){
var tabClickHandler = getTabClickHandler();

$document.off('click', '#SidePanel coral-tab');

$document.on('click', '#SidePanel coral-tab', function( eve ) {
var that = this;

if (initialData === getFormData()) {
tabClickHandler.call(that);
return;
}

var fui = $(window).adaptTo("foundation-ui");

fui.prompt("Confirm", "NEW Warning! You must save your work before navigating to a new screen. Click ok to go to the page without saving", "warning", [{
text: "Ok",
handler: function(){
tabClickHandler.call(that)
}
}, {
text: "Stay",
primary: true,
handler: function(){}
}]);
});
}

function getTabClickHandler(){
var handlers = $._data(document, "events")["click"];

return _.reject(handlers, function(handler){
return (handler.selector != "#SidePanel coral-tab" );
})[0].handler;
}

function getFragmentFormTabContent(){
return "<div class='sidepanel-tab sidepanel-tab-cf-form'></div>";
}

function openEditorPage(url){
var CFM = Dam.CFM,
href = Granite.HTTP.externalize(url);

$document.trigger(CFM.constants.EVENT_CONTENT_FRAGMENT_BLOCK, {
unloadHandling: true
});

href = href + encodeURI(CFM.state.fragment.path);

CFM.editor.Page.notifyNavigation(function(isSuccess) {
if (isSuccess) {
document.location.href = href;
}
});
}

function getFormData(){
var $fields = $("form").find("input[name]"),
data = [];

_.each($fields, function(field){
var $field = $(field), name = $field.attr("name");

if(name.includes("@")){
return;
}

data.push([name.substring(name.lastIndexOf("/") + 1)] + "=" + $field.val());
});

return data.join(",");
}

function saveInitialState(){
initialData = getFormData();
}

function workarounds(){
$.fn.rearrangeFormLayout = function () {};
}

workarounds();
})(jQuery, jQuery(document));




AEM 6530 - Core Components 280 - Touch UI RTE (Rich Text Editor) Dialog Color Font Plugin

$
0
0

Goal


Touch UI Color Picker Plugin for RTE (Rich Text Editor) Dialog - /libs/cq/gui/components/authoring/dialog/richtext

In Core Components 2.8.0 uiSettings/cui/dialogFullScreen was removed and the plugin settings were moved to Template Policies, this post is for making the Font & Color plugin work with Core Components 2.8.0

For a similar extension on 65 and older Core Components check this post 64 check this post 63 check this post

For demo purposes, design dialog of core text component v2 was modified to add the font picker configuration - /apps/core/wcm/components/text/v2/text/cq:design_dialog/content/items/tabs/items/plugins/items/experience-aem-fonts

Demo | Package Install | Github


Plugin Configuration in Design Dialog





Plugin Config in Template Policy





Plugin Picker



Font and Color








Solution


1) Login to CRXDE Lite, add nt:folder /apps/eaem-fonts-plugin

2) To show the color picker in a dialog create cq:Page /apps/eaem-fonts-plugin/font-selector and add  eaem.rte.font.plugincategories in /apps/eaem-fonts-plugin/font-selector/jcr:content/head/clientlibs

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Page">
<jcr:content
jcr:mixinTypes="[sling:VanityPath]"
jcr:primaryType="nt:unstructured"
jcr:title="EAEM Font Selector"
sling:resourceType="granite/ui/components/coral/foundation/page">
<head jcr:primaryType="nt:unstructured">
<favicon
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
<viewport
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
<clientlibs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral, eaem.rte.font.plugin]"/>
</head>
<body
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/page/body">
<items jcr:primaryType="nt:unstructured">
<form
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form"
class="foundation-form content-container"
maximized="{Boolean}true"
style="vertical">
<items jcr:primaryType="nt:unstructured">
<wizard
jcr:primaryType="nt:unstructured"
jcr:title="Select Text Font Color..."
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<text
jcr:primaryType="nt:unstructured"
jcr:title="Select Text Font Color..."
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<fixedColumns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<fontSize
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldDescription="Select text size"
fieldLabel="Size"
name="./size">
<items jcr:primaryType="nt:unstructured">
<def
jcr:primaryType="nt:unstructured"
text="Select Size"
value=""/>
<small
jcr:primaryType="nt:unstructured"
text="Small (15px)"
value="15px"/>
<medium
jcr:primaryType="nt:unstructured"
text="Medium (30px)"
value="30px"/>
<large
jcr:primaryType="nt:unstructured"
text="Large (40px)"
value="40px"/>
</items>
</fontSize>
<color
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
fieldDescription="Select text color"
fieldLabel="Text Color"
name="./color"
showProperties="{Boolean}true"/>
<bgColor
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
fieldDescription="Select background color"
fieldLabel="Background Color"
name="./bgColor"
showProperties="{Boolean}true"/>
</items>
</column>
</items>
</fixedColumns>
</items>
<parentConfig jcr:primaryType="nt:unstructured">
<prev
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
text="Cancel">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="cancel"/>
</prev>
<next
granite:class="foundation-wizard-control"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/button"
disabled="{Boolean}true"
text="Apply"
type="submit"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</text>
</items>
</wizard>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>


3) Create clientlib (cq:ClientLibraryFolder) /apps/eaem-fonts-plugin/clientlib set property categories to [cq.authoring.dialog.all, eaem.rte.font.plugin] and dependencies to [lodash]

4) Create file (nt:file) /apps/eaem-fonts-plugin/clientlib/js.txt, add the following content

                                    eaem-fonts.js


5) Create file (nt:file) /apps/eaem-fonts-plugin/clientlib/eaem-fonts.js, add the following code

(function($, CUI, $document){
var GROUP = "experience-aem-fonts",
FONT_FEATURE = "applyFont",
FONT_SIZE_FEATURE = "fontSize",
TEXT_COLOR_FEATURE = "textColor",
TEXT_BG_COLOR_FEATURE = "textBackgroundColor",
EAEM_APPLY_FONT_DIALOG = "eaemTouchUIApplyFontDialog",
SENDER = "experience-aem", REQUESTER = "requester", $eaemFontPicker,
CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
FONT_SELECTOR_URL = "/apps/eaem-fonts-plugin/font-selector.html",
url = document.location.pathname;

if( url.indexOf(FONT_SELECTOR_URL) == 0 ){
handlePicker();
return;
}

function handlePicker(){
$document.on("foundation-contentloaded", fillDefaultValues);

$document.on("click", CANCEL_CSS, sendCancelMessage);

$document.submit(sentTextAttributes);
}

function queryParameters() {
var result = {}, param,
params = document.location.search.split(/\?|\&/);

params.forEach( function(it) {
if (_.isEmpty(it)) {
return;
}

param = it.split("=");
result[param[0]] = param[1];
});

return result;
}

function setWidgetValue(form, selector, value, enable){
Coral.commons.ready(form.querySelector(selector), function (field) {
field.value = _.isEmpty(value) ? "" : decodeURIComponent(value);

if(enable){
delete field.disabled;
}else{
field.disabled = "disabled";
}
});
}

function fillDefaultValues(){
var queryParams = queryParameters(),
$form = $("form");

if(_.isEmpty(queryParams.features)){
return;
}

var features = queryParams.features.split(",");

setWidgetValue($form[0], "[name='./color']", queryParams.color, features.includes(TEXT_COLOR_FEATURE));

setWidgetValue($form[0], "[name='./size']", queryParams.size, features.includes(FONT_SIZE_FEATURE));

setWidgetValue($form[0], "[name='./bgColor']", queryParams.bgColor, features.includes(TEXT_BG_COLOR_FEATURE));

$form.css("background-color", "#fff");
}

function sentTextAttributes(){
var message = {
sender: SENDER,
action: "submit",
data: {}
}, $form = $("form"), $field;

_.each($form.find("[name^='./']"), function(field){
$field = $(field);
message.data[$field.attr("name").substr(2)] = $field.val();
});

getParent().postMessage(JSON.stringify(message), "*");
}

function sendCancelMessage(){
var message = {
sender: SENDER,
action: "cancel"
};

getParent().postMessage(JSON.stringify(message), "*");
}

function getParent() {
if (window.opener) {
return window.opener;
}

return parent;
}

addPlugin();

addPluginToDefaultUISettings();

addDialogTemplate();

function addDialogTemplate(){
var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;

var html = "<iframe width='700px' height='500px' frameBorder='0' src='" + url + "'></iframe>";

if(_.isUndefined(CUI.rte.Templates)){
CUI.rte.Templates = {};
}

if(_.isUndefined(CUI.rte.templates)){
CUI.rte.templates = {};
}

CUI.rte.templates['dlg-' + EAEM_APPLY_FONT_DIALOG] = CUI.rte.Templates['dlg-' + EAEM_APPLY_FONT_DIALOG] = Handlebars.compile(html);
}

function rgbToHex(color){
if(_.isEmpty(color)){
return color;
}

if(color.indexOf("rgb") == 0){
color = CUI.util.color.RGBAToHex(color);
}

return color;
}

function addPluginToDefaultUISettings(){
var groupFeature = GROUP + "#" + FONT_FEATURE,
toolbar = CUI.rte.ui.cui.DEFAULT_UI_SETTINGS.dialogFullScreen.toolbar;

if(toolbar.includes(groupFeature)){
return;
}

toolbar.splice(3, 0, groupFeature);
}

var EAEMApplyFontDialog = new Class({
extend: CUI.rte.ui.cui.AbstractDialog,

toString: "EAEMApplyFontDialog",

initialize: function(config) {
this.exec = config.execute;
},

getDataType: function() {
return EAEM_APPLY_FONT_DIALOG;
}
});

function addPlugin(){
var EAEMTouchUIFontPlugin = new Class({
toString: "EAEMTouchUIFontPlugin",

extend: CUI.rte.plugins.Plugin,

pickerUI: null,

getFeatures: function() {
return [ FONT_FEATURE ];
},

initializeUI: function(tbGenerator) {
var plg = CUI.rte.plugins;

addPluginToDefaultUISettings();

if (!this.isFeatureEnabled(FONT_FEATURE)) {
return;
}

this.pickerUI = tbGenerator.createElement(FONT_FEATURE, this, false, { title: "Select Font..." });
tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 10);

var groupFeature = GROUP + "#" + FONT_FEATURE;
tbGenerator.registerIcon(groupFeature, "abc");
},

execute: function (pluginCommand, value, envOptions) {
var context = envOptions.editContext,
ek = this.editorKernel;

if (pluginCommand != FONT_FEATURE) {
return;
}

if(!isValidSelection()){
return;
}

var selection = CUI.rte.Selection.createProcessingSelection(context),
startNode = selection.startNode;

if ( (selection.startOffset === startNode.length) && (startNode != selection.endNode)) {
startNode = startNode.nextSibling;
}

var $tag = $(CUI.rte.Common.getTagInPath(context, startNode, "span")),
size = $tag.css("font-size"),dialog,dm = ek.getDialogManager(),
$container = CUI.rte.UIUtils.getUIContainer($(context.root)),
propConfig = {
'parameters': {
'command': this.pluginId + '#' + FONT_FEATURE
}
};

var color = this.getColorAttributes($tag);

if(this.eaemApplyFontDialog){
dialog = this.eaemApplyFontDialog;

dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl(this.config.features, size, color.color, color.bgColor));
}else{
dialog = new EAEMApplyFontDialog();

dialog.attach(propConfig, $container, this.editorKernel);

dialog.$dialog.css("-webkit-transform", "scale(0.9)").css("-webkit-transform-origin", "0 0")
.css("-moz-transform", "scale(0.9)").css("-moz-transform-origin", "0px 0px");

dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl(this.config.features, size, color.color, color.bgColor));

this.eaemApplyFontDialog = dialog;
}

dm.show(dialog);

$(window).off('message', receiveMessage).on('message', receiveMessage);

function isValidSelection(){
var winSel = window.getSelection();
return winSel && winSel.rangeCount == 1 && winSel.getRangeAt(0).toString().length > 0;
}

function receiveMessage(event) {
event = event.originalEvent || {};

if (_.isEmpty(event.data)) {
return;
}

var message, action;

try{
message = JSON.parse(event.data);
}catch(err){
return;
}

if (!message || message.sender !== SENDER) {
return;
}

action = message.action;

if(action === "submit"){
ek.relayCmd(pluginCommand, message.data);
}

dialog.hide();
}
},

getColorAttributes: function($tag){
var key, color = { color: "", bgColor : ""};

if(!$tag.attr("style")){
return color;
}

//donot use .css("color"), it returns default font color, if color is not set
var parts = $tag.attr("style").split(";");

_.each(parts, function(value){
value = value.split(":");

key = value[0] ? value[0].trim() : "";
value = value[1] ? value[1].trim() : "";

if(key == "color"){
color.color = rgbToHex(value);
}else if(key == "background-color"){
color.bgColor = rgbToHex(value);
}
});

return color;
},

showFontModal: function(url){
var self = this, $iframe = $('<iframe>'),
$modal = $('<div>').addClass('eaem-cfm-font-size coral-Modal');

$iframe.attr('src', url).appendTo($modal);

$modal.appendTo('body').modal({
type: 'default',
buttons: [],
visible: true
});

$eaemFontPicker = $modal;

$eaemFontPicker.eaemFontPlugin = self;

$modal.nextAll(".coral-Modal-backdrop").addClass("cfm-coral2-backdrop");
},

getPickerIFrameUrl: function(features, size, color, bgColor){
var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;

url = url + "&features=" + features.join(",");

if(!_.isEmpty(color)){
url = url + "&color=" + encodeURIComponent(color);
}

if(!_.isEmpty(bgColor)){
url = url + "&bgColor=" + encodeURIComponent(bgColor);
}

if(!_.isEmpty(size)){
url = url + "&size=" + size;
}

return url;
},

updateState: function(selDef) {
var hasUC = this.editorKernel.queryState(FONT_FEATURE, selDef);

if (this.pickerUI != null) {
this.pickerUI.setSelected(hasUC);
}
}
});

var EAEMTouchUIFontCmd = new Class({
toString: "EAEMTouchUIFontCmd",

extend: CUI.rte.commands.Command,

isCommand: function (cmdStr) {
return (cmdStr.toLowerCase() == FONT_FEATURE);
},

getProcessingOptions: function () {
var cmd = CUI.rte.commands.Command;
return cmd.PO_SELECTION | cmd.PO_BOOKMARK | cmd.PO_NODELIST;
},

getTagObject: function(textData) {
var style = "";

if(!_.isEmpty(textData.color)){
style = "color: " + textData.color + ";";
}

if(!_.isEmpty(textData.size)){
style = style + "font-size: " + textData.size + ";";
}

if(!_.isEmpty(textData.bgColor)){
style = style + "background-color: " + textData.bgColor;
}

return {
"tag": "span",
"attributes": {
"style" : style
}
};
},

execute: function (execDef) {
var textData = execDef.value, selection = execDef.selection,
nodeList = execDef.nodeList;

if (!selection || !nodeList) {
return;
}

var common = CUI.rte.Common,
context = execDef.editContext,
tagObj = this.getTagObject(textData);

if(_.isEmpty(textData.size) && _.isEmpty(textData.color) && _.isEmpty(textData.bgColor)){
nodeList.removeNodesByTag(execDef.editContext, tagObj.tag, undefined, true);
return;
}

var tags = common.getTagInPath(context, selection.startNode, tagObj.tag);

//remove existing color before adding new color
if (tags != null) {
nodeList.removeNodesByTag(execDef.editContext, tagObj.tag, tags.attributes ? tags.attributes : undefined, true);
}

nodeList.surround(execDef.editContext, tagObj.tag, tagObj.attributes);
},

queryState: function(selectionDef, cmd) {
return false;
}
});

CUI.rte.commands.CommandRegistry.register(FONT_FEATURE, EAEMTouchUIFontCmd);

CUI.rte.plugins.PluginRegistry.register(GROUP,EAEMTouchUIFontPlugin);
}
}(jQuery, window.CUI,jQuery(document)));

AEM 6530 - Touch UI Date Picker save Date Time in UTC

$
0
0

Goal


AEM Page Properties date picker saves the date set (On Time / Off Time) in User's Time Zone in CRX. This post is to save the date time in UTC, whereas show it in user's time zone...

Demo | Package Install | Github


Product

                 Date saved in user's time zone (CST in screenshot below)



Extension

                 Set the property eaem-save-in-utc = true                                           

                 e.g./libs/wcm/foundation/components/basicpage/v1/basicpage/tabs/basic/items/column/items/onofftime/items/ondate/granite:data



                 Date saved in UTC in CRX


                 Date shown in Browser



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-save-date-timezone

2) Create node /apps/eaem-save-date-timezone/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [cq.authoring.dialog], String[] property dependencies with value lodash.

3) Create file (nt:file) /apps/eaem-save-date-timezone/clientlib/js.txt, add

                        timezone-date.js

4) Create file (nt:file) /apps/eaem-save-date-timezone/clientlib/timezone-date.js, add the following code

(function($, CUI, $document){
var EAEM_SAVE_IN_UTC = "eaem-save-in-utc";

$document.on("foundation-contentloaded", handleDatePickers);

function handleDatePickers(){
var $datePickers = $("coral-datepicker");

if(_.isEmpty($datePickers)){
return;
}


_.each($datePickers, function(datePicker){
var $datePicker = $(datePicker);

if(!$datePicker.data(EAEM_SAVE_IN_UTC)){
return;
}

$datePicker.on("change", setTimeZone);

setTimeZone.call(datePicker);
});
}

function setTimeZone(){
var datePicker = this, $datePicker = $(this),
$timeZone = $datePicker.nextAll(".granite-datepicker-timezone");

if(!datePicker.value){
$timeZone.attr("hidden", "hidden");
return;
}

var toUTC = new Date(datePicker.value).toISOString(),
message = "Date shown in your timezone, but saved in UTC as " + toUTC;

$datePicker.find("[name^='./']").val(toUTC);

$timeZone.removeAttr("hidden");

$timeZone.find("span").html(message);
}

}(jQuery, window.CUI,jQuery(document)));

AEM 6530 - Touch UI Page Properties On Time, Off Time Date Picker save and show in specific Time Zone

$
0
0

Goal


AEM Page Properties date picker saves the date set (On Time / Off Time) in User's Time Zone in CRX. This post is to save and show the date time in specific configured time zone...

Demo | Package Install | Github


Product

                 Date saved in user's time zone (CST in screenshot below)




Extension

                 For PST, set the property eaem-offset = -08:00

                 For PST, set the property eaem-offset-message = You are selecting the date in PST

                 e.g./libs/wcm/foundation/components/basicpage/v1/basicpage/tabs/basic/items/column/items/onofftime/items/ondate/granite:data

                 ("/libs" modified for demo purposes only....)




                 Date saved in PST in CRX




                 Date shown in PST in Browser




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-save-date-set-offset

2) Create node /apps/eaem-save-date-set-offset/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [cq.authoring.dialog], String[] property dependencies with value lodash.

3) Create file (nt:file) /apps/eaem-save-date-set-offset/clientlib/js.txt, add

                        timezone-offset.js

4) Create file (nt:file) /apps/eaem-save-date-set-offset/clientlib/timezone-offset.js, add the following code

(function($, CUI, $document){
var EAEM_OFFSET = "eaem-offset",
EAEM_OFFSET_MESSAGE = "eaem-offset-message";

$document.on("foundation-contentloaded", handleDatePickers);

function handleDatePickers(){
var $datePickers = $("coral-datepicker");

if(_.isEmpty($datePickers)){
return;
}

_.each($datePickers, function(datePicker){
var $datePicker = $(datePicker);

if(!$datePicker.data(EAEM_OFFSET)){
return;
}

if(datePicker.valueFormat !== "YYYY-MM-DD[T]HH:mm:ss.SSSZ"){
return;
}

$datePicker.on("change", setOffset);

setOffset.call(datePicker, {}, $datePicker.attr("value"));
});
}

function setOffset(event, initValue){
var datePicker = this, $datePicker = $(this),
offset = $datePicker.data(EAEM_OFFSET), value,
$timeZone = $datePicker.nextAll(".granite-datepicker-timezone");

if(!datePicker.value){
$timeZone.attr("hidden", "hidden");
return;
}

if(_.isUndefined(initValue)){
value = getDateTimePortion(datePicker.value) + offset;
}else{
value = initValue;

datePicker.value = getDateTimePortion(value) + moment(new Date()).format("Z");
}

var message = $datePicker.data(EAEM_OFFSET_MESSAGE);

message = message || "Date shown (and saved) is offset to " + $datePicker.data(EAEM_OFFSET) + " relative to your timezone";

$datePicker.find("[name^='./']").val(value);

$timeZone.removeAttr("hidden");

$timeZone.find("span").html(message);
}

function getDateTimePortion(value){
if(value.endsWith("Z")){
value = value.substring(0, value.indexOf("Z"));
}else{
value = value.substring(0, value.lastIndexOf("-"));
}

return value;
}
}(jQuery, window.CUI,jQuery(document)));

AEM 6530 - Assets Admin Search Rail Predicate to search based on Asset ID (jcr:uuid)

$
0
0

Goal


Add an Assets search rail predicate to search using jcr:uuid (index available otb /oak:index/uuid)

Query executed is something like below

/jcr:root/content/dam/texas//element(*, dam:Asset)[(@jcr:uuid = 'e164529c-2ea2-4925-9ad5-0c443ec57ee3')]

Demo | Package Install | Github


Asset ID Filter in Rail




Assets Search form Configuration


      http://localhost:4502/libs/cq/core/content/tools/customsearch.html/libs/settings/dam/search/facets/assets/jcr:content








Solution


1) Login to CRXDe Lite and add the Asset ID predicate configuration eaemjcruuid in /apps/settings/dam/search/facets/formbuilderconfig/predicatetypes/items

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="cq:Page">
<adminui/>
<search jcr:primaryType="nt:unstructured">
<facets jcr:primaryType="nt:unstructured">
<formbuilderconfig jcr:primaryType="nt:unstructured">
<predicatetypes jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<eaemjcruuid
jcr:primaryType="nt:unstructured"
fieldLabel="Assed ID (jcr:uuid)"
fieldPropResourceType="/apps/eaem-search-form-jcruuid-predicate/jcruuid-field"
fieldResourceType="/apps/eaem-search-form-jcruuid-predicate/jcruuid-predicate"
fieldTitle="Experience AEM Asset ID Predicate"
fieldViewResourceType="granite/ui/components/foundation/form/textfield"
renderType="text"/>
</items>
</predicatetypes>
</formbuilderconfig>
</facets>
</search>
</jcr:root>


2) Create the configuration page for predicate (fieldPropResourceType in step 1) /apps/eaem-search-form-jcruuid-predicate/jcruuid-field/jcruuid-field.jsp with the following code

<%@include file="/libs/granite/ui/global.jsp" %>
<%@ page session="false" contentType="text/html" pageEncoding="utf-8"
import="com.adobe.granite.ui.components.Config" %>

<%
String key = resource.getName();
String metaType = "eaemjcruuid";

String fieldLabel = i18n.get("Asset ID");
%>

<input type="hidden" name="<%= xssAPI.encodeForHTMLAttr("./items/" + key) %>">
<input type="hidden" name="<%= xssAPI.encodeForHTMLAttr("./items/" + key + "/jcr:primaryType") %>" value="nt:unstructured">
<input type="hidden" name="<%= xssAPI.encodeForHTMLAttr("./items/" + key + "/sling:resourceType") %>" value="/apps/eaem-search-form-jcruuid-predicate/jcruuid-predicate">
<input type="hidden" name="<%= xssAPI.encodeForHTMLAttr("./items/" + key + "/fieldLabel") %>" value="<%= fieldLabel %>">
<input type="hidden" name="<%= xssAPI.encodeForHTMLAttr("./items/" + key + "/metaType") %>" value="<%= metaType %>">

<div><h3><%= i18n.get("Asset ID (jcr:uuid) predicate")%></h3></div>

<% request.setAttribute ("com.adobe.cq.datasource.fieldtextplaceholder", i18n.get("Asset ID"));%>

<sling:include resource="<%= resource %>" resourceType="dam/gui/coral/components/admin/customsearch/formbuilder/predicatefieldproperties/fieldlabelpropertyfield"/>

<sling:include resource="<%= resource %>" resourceType="granite/ui/components/foundation/form/formbuilder/formfieldproperties/titlefields"/>

<sling:include resource="<%= resource %>" resourceType="granite/ui/components/foundation/form/formbuilder/formfieldproperties/deletefields"/>


3) Create the predicate widget render html (fieldResourceType in step 1) /apps/eaem-search-form-jcruuid-predicate/jcruuid-predicate/jcruuid-predicate.jsp with following code

<%@include file="/libs/granite/ui/global.jsp" %>
<%@ page session="false" contentType="text/html" pageEncoding="utf-8"
import="com.adobe.granite.ui.components.Config"%>
<%@ page import="com.adobe.granite.ui.components.AttrBuilder" %>

<%
Config cfg = new Config(resource);
String name = cfg.get("text", i18n.get("Property"));
String metaType = cfg.get("metaType", "eaemjcruuid");

boolean foldableOpen = cfg.get("open", true);
String selected = foldableOpen ? "selected":"";

AttrBuilder inputAttrs = new AttrBuilder(request, xssAPI);
inputAttrs.add("type", "text");
inputAttrs.add("name", metaType);
inputAttrs.addClass("coral-Form-field coral-Textfield coral-DecoratedTextfield-input");
inputAttrs.add("placeholder", "Asset ID (jcr:uuid)");
%>
<coral-accordion variant="large">
<coral-accordion-item "<%=selected%>" data-metaType="checkboxgroup" data-type="eaemjcruuid">
<coral-accordion-item-label><%= xssAPI.encodeForHTML(name) %></coral-accordion-item-label>
<coral-accordion-item-content class="coral-Form coral-Form--vertical">
<input <%=inputAttrs.build()%>>
</coral-accordion-item-content>
</coral-accordion-item>
</coral-accordion>


4) Add the predicate evaluator apps.experienceaem.assets.JcrUUIDPredicateEvaluator for searching using jcr:uuid

package apps.experienceaem.assets;

import com.day.cq.search.Predicate;
import com.day.cq.search.eval.AbstractPredicateEvaluator;
import com.day.cq.search.eval.EvaluationContext;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;

@Component(
factory = "com.day.cq.search.eval.PredicateEvaluator/eaemjcruuid"
)public class JcrUUIDPredicateEvaluator extends AbstractPredicateEvaluator {
public String getXPathExpression(Predicate predicate, EvaluationContext context) {
String value = predicate.get(predicate.getName());

if(StringUtils.isEmpty(value)){
return null;
}

return "(@jcr:uuid = '" + value + "')";
}
}

AEM 6530 - Touch UI add numbering to Multifield Items

$
0
0

Goal


Add numbers to composite multifield items for easy identification...

Demo | Package Install | Github


Product



Extension



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touchui-multifield-items-numbering

2) Create node /apps/eaem-touchui-multifield-items-numbering/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [cq.authoring.dialog.all], String[] property dependencies with value lodash.

3) Create file (nt:file) /apps/eaem-touchui-multifield-items-numbering/clientlib/js.txt, add

                        mf-items-numbering.js

4) Create file (nt:file) /apps/eaem-touchui-multifield-items-numbering/clientlib/mf-items-numbering.js, add the following code

(function ($, $document) {
var COMPOSITE_MULTIFIELD_SELECTOR = "coral-multifield[data-granite-coral-multifield-composite]";

$document.on("foundation-contentloaded", addNumbering);

function addNumbering(){
_.each( [COMPOSITE_MULTIFIELD_SELECTOR], function(mfSel){
var $mField = $(mfSel);

$mField.on("coral-collection:add coral-collection:remove", function(event){
var $mField = $(this);

Coral.commons.ready(event.detail.item, function(){
numberMFItem($mField);
});
});

numberMFItem($mField);
});

function numberMFItem($mField){
var $mfContentItems = $mField.find("coral-multifield-item-content");

_.each($mfContentItems, function(mfContentItem, index){
var $mfContentItem = $(mfContentItem);

if(_.isEmpty($mfContentItem.find(".eaem-mf-counter"))){
$mfContentItem.wrapInner('<div style="display:inline-block; margin-left: 5px; width: 96%">');
}else{
$mfContentItem.find(".eaem-mf-counter").remove();
}

$mfContentItem.prepend(getIndexHtml(index + 1));
});
}

function getIndexHtml(index){
return '<div class="eaem-mf-counter" style="display:inline-block; width: 2%; vertical-align: top; ">' +
'<label class="coral-Form-fieldlabel">'
+ index +
'.</label>' +
'</div>'
}
}
}(jQuery, jQuery(document), Granite.author));

AEM 6530 - Quick Instructions to Install AEM with S3 Datastore on CentOS

$
0
0

Goal


Install AEM 65 on CentOS with Amazon S3 bucket configured as Datastore

For more detail check aem documentation

Thank you Bryan Stopp for the help with aem service commands


Solution


1) Assuming you are accessing the CentOS server from Windows client using ssh, set the following in  C:\Users\<user>\.ssh\config so the terminal doesn't timeout

                              ServerAliveInterval 120

2) Assuming you have an S3 bucket available with  name experience-aem-65 in region us-east-1

3) Download aws s3 connector from adobe repo e.g. https://repo.adobe.com/nexus/content/groups/public/com/adobe/granite/com.adobe.granite.oak.s3connector/1.10.8/

4) Upload the jdk jar, aem 65 jar, license.properties, s3 connector zip to a location on the server e.g. /mnt/crx/packages (here i used Filezilla)



5) Install Java

                              > sudo su
                              > mkdir /mnt/java
                              > cd /mnt/java
                              > tar -zxf /mnt/crx/packages/jdk-8u241-linux-x64.tar.gz -C .
                              > sudo update-alternatives --install /usr/bin/java java /mnt/java/jdk1.8.0_241/bin/java 1
                              > sudo vi ~/.bashrc
                              # Set java environment
                              export JAVA_HOME=/mnt/java/jdk1.8.0_241
                              export PATH=${PATH}:${JAVA_HOME}/bin

6) Make sure you have read and write access to the S3 bucket (generally a IAM role is configured on the box to access S3 without credentials)
                              
                              > aws s3api list-objects --bucket experience-aem-65
                              > aws s3api put-object --bucket experience-aem-65 --key test --body some_text_file.txt

7) Unpack the AEM S3 bundle on server

                              > unzip com.adobe.granite.oak.s3connector-1.10.8.zip -d /mnt/crx/packages/s3

8) Unpack AEM

                              > sudo su
                              > cp /mnt/crx/packages/cq-quickstart-6.5.0-p4502.jar /mnt/crx/author/
                              > cp /mnt/crx/packages/license.properties /mnt/crx/author/
                              > cd /mnt/cr/author
                              > java -jar cq-quickstart-6.5.0-p4502.jar -unpack 

9) Copy the S3 bundle jars and config files

                              > sudo su
                              > cp -R /mnt/crx/packages/s3/jcr_root/libs/system/install /mnt/crx/author/crx-quickstart/install
                              > cp /mnt/crx/packages/s3/jcr_root/libs/system/config/org.apache.jackrabbit.oak.plugins.blob.datastore.S3DataStore.config /mnt/crx/author/crx-quickstart/install
                              > cp /mnt/crx/packages/s3/jcr_root/libs/system/config/org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.config /mnt/crx/author/crx-quickstart/install
                              > chmod -R 777 /mnt/crx/author

10) Edit the S3 datastore config file to provide the following bucket info eg. /mnt/crx/author/crx-quickstart/install/org.apache.jackrabbit.oak.segment.SegmentNodeStoreService.config

The following properties assume S3 IAM role was configured, otherwise you need to provide accessKey and secretKey as well. The other property secret is for sharing this data store with publish for binaryless replication

                              s3Bucket="experience-aem-65"
                              s3Region="us-east-1"
                              secret="eaem"

11) Install AEM with runmode crx3tar-nofds

                              > sudo su
                              > cd /mnt/crx/author
                              > java -jar cq-quickstart-6.5.0-p4502.jar -r crx3tar-nofds

12) To run AEM with service user

                              > sudo groupadd aem
                              > sudo useradd -M aem -g aem
                              > sudo chown -R aem:aem /mnt/crx/author
                              > vi /lib/systemd/system/aem.service
                                               [Unit]
                                               Description=Adobe Experience Manager
                                               After=network.target remote-fs.target nss-lookup.target

                                               [Service]
                                               Type=forking
                                               PIDFile=/aem/author/crx-quickstart/crx-quickstart/conf/cq.pid
                                               User=aem
                                               Group=aem
                                               ExecStart=/aem/author/crx-quickstart/crx-quickstart/bin/start
                                               ExecStop=/aem/author/crx-quickstart/crx-quickstart/bin/stop

                                               # We want systemd to give aem some time to finish gracefully, but still want
                                               # it to kill aem after TimeoutStopSec if something went wrong during the
                                               # graceful stop. Normally, Systemd sends SIGTERM signal right after the
                                               # ExecStop, which would kill aem. We are sending useless SIGCONT here to give
                                               # aem time to finish.

                                               TimeoutStopSec=4min
                                               KillSignal=SIGCONT
                                               PrivateTmp=true

                                               [Install]
                                               WantedBy=multi-user.target
                              > sudo chmod 644 /lib/systemd/system/aem.service
                              > sudo systemctl enable aem.service

13) Commands to run AEM

                              Start AEM:  sudo systemctl start aem
                              Stop AEM:  sudo systemctl stop aem
                              AEM Status:  sudo systemctl status aem





AEM 6540 - Accessing AEM Assets in ACS (Adobe Campaign Standard) using Assets Core Service for Emails

$
0
0

Goal


Configure OnPrem AEM Assets Instance to share Images with Asset Core Service for using them in emails created and delivered via ACS (Adobe Campaign Standard)

For product documentation check this post


Solution


Integrating AEM Assets with Assets Core Service

1) Assuming you are provisioned for Assets Core Service and Adobe Campaign Standard in Experience Cloud, the following icons should be active in solution switcher...



2) Following are the URLs for instances used...

                      AEM: http://localhost:4502/assets.html
                      ACS: https://acs959.msavlab.adobe.com/
                      Assets Core Service: https://ags959.experiencecloud.adobe.com/assets.html/content/dam/mac/ags959

3) Login to https://legacy-oauth.cloud.adobe.io/login with your Adobe ID to create a JWT application for integrating the two products - Local OnPrem AEM and Assets Core Services

4) Select your org and click Add Application

5) Enter the details and click Add, for eg.

                          Application Name : Experience AEM Assets Core Sync Service
                          Organization: Adobe AGS959
                          Scope: cc-share, dam-read, dam-sync, dam-write



6) Your application is now created, copy the Application ID



7) Login to local OnPrem AEM and access Tools> Cloud Services> Legacy Cloud Services




8) Click Configure Now in Adobe Experience Cloud section



9) Enter any title eg. Experience AEM Assets Core Sync, click Create



10) Enter the details for Tenant info and click Ok (creating the configuration in /etc/cloudservices/marketingcloud/experience-aem-assets-core-sync-service), for eg.

                         Tenant URL: https://ags959.experiencecloud.adobe.com
                         Client ID:  fxxxxxxxxx-experience-aem-assets-core-service (Application ID from JWT app above)
                         Synchronization is: enabled



11) Click Display Public Key in the configuration and copy the resulting content



12) Paste the Public key in JWT application created in Step 5 and click Save



13) Test your sync by accessing AEM Assets and sharing a folder with Experience Cloud (in the screen shot below, drop down shows Marketing Cloud)



14) You should see the assets synced to Assets core service of your org eg. https://ags959.experiencecloud.adobe.com/assets.html/content/dam/mac/ags959/experience-aem




Accessing Assets in ACS

1) Integrating AEM with Assets Core Service, the images are now shared and can be used in Email campaigns created using Campaign Standard (ACS)

2) Access ACS instance by clicking on the link in Experience Cloud (https://experience.adobe.com/) solution switcher

3) There are a number of ways to get customer profiles into ACS, assuming you don't have any customer profiles yet (to send an email), from the Home screen click on Customer profiles and click Create



4) Enter the profile details



5) From the Home screen select Create an Email, click Next



6) Select Send via Email, click Next



7) Enter email properties, eg. Label, click Next

8) Drag Select a profile and select the profile created in step 2 above, click Next



9) Select new Use the Email Designer option



10) Select 1:1 column from Structure Components, drag Image from Content Components and Click Open assets core service



11) Browse and select the asset from picker and click Save



12) Click on Prepare to run the prechecks, find number of deliveries etc.



13) Click Confirm to send the email







AEM 6540 - Hide Tag Root Paths in Metadata Editor of AEM Assets

$
0
0

Goal


When the Tag paths are too deep and an asset is tagged with tens of tags the UI may look cluttered, so you may want to hide the root paths which are obvious and generally customer specific....

Demo | Package Install | Github


Product



Extension



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-hide-tag-root-paths

2) Create node /apps/eaem-hide-tag-root-paths/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [dam.gui.coral.metadataeditor], String[] property dependencies with value lodash.

3) Create file (nt:file) /apps/eaem-hide-tag-root-paths/clientlib/js.txt, add

                        hide-root-paths.js

4) Create file (nt:file) /apps/eaem-hide-tag-root-paths/clientlib/hide-root-paths.js, add the following code

(function($, $document) {
var HIDE_TAGS_WITH_PREFIX = "we-retail",
HIDE_LEVELS = 2;

$document.on("foundation-contentloaded", hideRootPaths);

function hideRootPaths(){
var $autoCompletes = $("foundation-autocomplete");

if(_.isEmpty($autoCompletes)){
return;
}

_.each($autoCompletes, function(autoComplete){
if(autoComplete.eaemExtended){
return;
}

extendDisplay(autoComplete);

extendPicker(autoComplete);

autoComplete.eaemExtended = true;
});
}

function extendPicker(autoComplete){
$(autoComplete).on("change", function(){
extendDisplay(this);
})
}

function extendDisplay(autoComplete){
var $autoComplete = $(autoComplete),
$tags = $autoComplete.find("coral-tag");

_.each($tags, function(tag){
var $tag = $(tag), tagDisplayValue,
$tagLabel = $tag.find("coral-tag-label");

if(!$tag.attr("value").trim().startsWith(HIDE_TAGS_WITH_PREFIX)){
return;
}

tagDisplayValue = _.isEmpty($tagLabel) ? $tag.html() : $tagLabel.html();

if(_.isEmpty($tagLabel)){
$tag.html(cutLevels(tagDisplayValue));
}else{
$tagLabel.html(cutLevels(tagDisplayValue));
}
});
}

function cutLevels(tagValue){
var tagRetValue = tagValue;

for (var index = 0 ; index < HIDE_LEVELS; index++){
if(index == 0){
tagRetValue = tagRetValue.substring(tagRetValue.indexOf(":") + 1);
}else if(tagRetValue.includes("/")){
tagRetValue = tagRetValue.substring(tagRetValue.indexOf("/") + 1);
}

tagRetValue = tagRetValue.trim();
}

return tagRetValue;
}
}(jQuery, jQuery(document)));

AEM 6540 - Show File size in units in Assets Metadata Editor

$
0
0

Goal


Adding file size (./jcr:content/metadata/dam:size) field in metadata editor shows the size in bytes. The simple logic in this extension is for showing size in other units (KB, MB, GB etc.)

Thank you internet for the conversion code snippet

Package Install | Github


Product



Extension



Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-assets-metadata-file-size-units

2) Create node /apps/eaem-assets-metadata-file-size-units/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [dam.gui.coral.metadataeditor], String[] property dependencies with value lodash.

3) Create file (nt:file) /apps/eaem-assets-metadata-file-size-units/clientlib/js.txt, add

                        file-size-units.js

4) Create file (nt:file) /apps/eaem-assets-metadata-file-size-units/clientlib/file-size-units.js, add the following code

(function($, $document) {
var FILE_SIZE_NAME = "./jcr:content/metadata/dam:size",
initialized = false;

$document.on("foundation-contentloaded", init);

function init(){
if(initialized){
return;
}

initialized = true;

convertFileSize();
}

function convertFileSize(){
var $damSize = $("[name='" + FILE_SIZE_NAME + "']");

if(_.isEmpty($damSize)){
return;
}

var sizeInBytes = $damSize.val();

$damSize.val(!sizeInBytes ? "Unavailable" : formatBytes(parseInt(sizeInBytes), 2));
}

function formatBytes(bytes, decimals) {
if (bytes === 0){
return '0 Bytes';
}

const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));

return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + '' + sizes[i];
}
}(jQuery, jQuery(document)));

AEM 6540 - Using Asset Selector with DMS7 Dynamic Renditions in external CMS or thirdypary sites

$
0
0

Goal


Assuming AEM Assets is the image repository and the usecase is to make use of these assets managed in AEM, in an external CMS (not AEM sites) or a thirdparty website, Assets Selector provides a nice interface for browse/search & selecting the assets

Combined with Dynamic Media Smart Crop, you can generate dynamic rendition urls (scene7 delivery urls) for creating intelligent responsive crops for various device breakpoints, replacing the need for static renditions (eg. doing manual cropping using photoshop)...

For setting up AEM with Dynamic Media Scene7 check this post

Demo | Package Install | Github



Solution


1) Assuming AEM is setup with Dynamic Media Scene7 (check this post for how to) create the Image Profiles by accessing Tools > Assets > Image Profiles or http://localhost:4502/mnt/overlay/dam/gui/content/processingprofilepage/imageprocessingprofiles.html/conf/global/settings/dam/adminui-extension/imageprofile

2) Click Create to create an Image Profile,  give it a name, select the type as Smart Crop, add Responsive Image Crop breakpoints and click Save



3) Optionally create few processing profiles for customer specific purposes (only one image processing profile can be active on a folder ). The profiles are shown in asset selector page created to show image dynamic renditions...



4) Assign the Image Profile to a folder by selecting it and clicking Apply Processing Profile to Folder(s)



5) For accessing and showing the profiles in an external page, create read only authentication handler apps.experienceaem.assets.EAEMReadonlyAuthenticationHandler with the following code

package apps.experienceaem.assets;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.core.spi.AuthenticationInfo;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static org.osgi.framework.Constants.SERVICE_RANKING;

@Component(
service = { AuthenticationHandler.class, Filter.class },
immediate = true,
property = {
SERVICE_RANKING + ":Integer=" + 9999,
AuthenticationHandler.PATH_PROPERTY + "=/content/dam",
AuthenticationHandler.PATH_PROPERTY + "=/conf",
"service.description=Experience AEM Ready only Authentication Handler",
"sling.filter.scope=REQUEST"
})
@Designate(ocd = EAEMReadonlyAuthenticationHandler.Configuration.class)
public class EAEMReadonlyAuthenticationHandler implements AuthenticationHandler, Filter {
private static final Logger log = LoggerFactory.getLogger(EAEMReadonlyAuthenticationHandler.class);

private static String AUTH_TYPE_ASSET_SELECTOR = "EAEM_ASSET_SELECTOR";

private static final String SESSION_REQ_ATTR = EAEMReadonlyAuthenticationHandler.class.getName() + ".session";

private String readOnlyUser = "";

@Reference
private SlingRepository repository;

@Reference(target = "(service.pid=com.day.crx.security.token.impl.impl.TokenAuthenticationHandler)")
private AuthenticationHandler wrappedAuthHandler;

@Activate
protected void activate(final Configuration config) {
readOnlyUser = config.read_only_user();
}

public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) {
AuthenticationInfo authInfo = null;

String authType = request.getParameter("authType");

if(StringUtils.isEmpty(authType) || !authType.equals(AUTH_TYPE_ASSET_SELECTOR)){
return wrappedAuthHandler.extractCredentials(request, response);
}

Session adminSession = null;

try{
adminSession = repository.loginAdministrative(null);

Session userSession = adminSession.impersonate(new SimpleCredentials(readOnlyUser, new char[0]));

request.setAttribute(SESSION_REQ_ATTR, userSession);

authInfo = new AuthenticationInfo(AUTH_TYPE_ASSET_SELECTOR);

authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, userSession);
}catch(Exception e){
log.error("Error could not create session for authType - " + authType, e);
return AuthenticationInfo.FAIL_AUTH;
}finally {
if (adminSession != null) {
adminSession.logout();
}
}

return authInfo;
}

public boolean requestCredentials(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
return wrappedAuthHandler.requestCredentials(httpServletRequest, httpServletResponse);
}

public void dropCredentials(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
wrappedAuthHandler.dropCredentials(httpServletRequest, httpServletResponse);
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Session userSession = (Session) request.getAttribute(EAEMReadonlyAuthenticationHandler.SESSION_REQ_ATTR);

if (userSession != null) {
request.removeAttribute(EAEMReadonlyAuthenticationHandler.SESSION_REQ_ATTR);
}

chain.doFilter(request, response);

if (userSession != null) {
userSession.logout();
}
}

public void init(FilterConfig filterConfig) throws ServletException {
}

public void destroy() {
}

@ObjectClassDefinition(name = "EAEMReadonlyAuthenticationHandler Configuration")
public @interface Configuration {

@AttributeDefinition(
name = "Read only user",
description = "Read only user for accessing asset metadata json",
type = AttributeType.STRING)
String read_only_user() default "eaem-read-only-user";
}
}

6) The authentication handler provides readonly access to nodes in paths /content/dam and /conf, when the url contains query parameter authType=EAEM_ASSET_SELECTOR.It needs a read only user to impersonate, so create the user eg. eaem-read-only-user, provide read only permission on /content/dam and /conf eg. http://localhost:4502/security/permissions.html/principal/eaem-read-only-user?filter=user



7) Configure the read only user in the EAEMReadonlyAuthenticationHandler osgi properties http://localhost:4502/system/console/configMgr/apps.experienceaem.assets.EAEMReadonlyAuthenticationHandler



8) Authentication handler uses repository.loginAdministrative(null) to get an admin session. To allow administrative login in code whitelist the bundle (best practice is to get a session using service user)



9) Since the calls to asset metadata in AEM are made from an external site, set the CORS policy to allow origin Access-Control-Allow-Origin



10) Create the standalone page for loading asset selector and showing dynamic renditions eg. eaem-asset-selector-dynamic-renditions\asset-selector\asset-selector.html. It can be written using any javascript framework (eg. just jQuery), the following sample uses CoralUI

<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale = 1.0,maximum-scale = 1.0">
<title>Experience AEM Asset Selector</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<script src="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/js/libs/moment.js"></script>
<script src="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/js/libs/jquery.js"></script>
<script src="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/js/coral.js"></script>
<script src="asset-selector.js"></script>
<link rel="stylesheet" type="text/css"
href="https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/coral-ui/coralui3/css/coral.css">
<link rel="stylesheet" type="text/css" href="asset-selector.css">
</head>
<body>
<div>
<h1 class="coral-Heading" style="text-align: center">AEM Dynamic Renditions Selector</h1>
</div>
<div>
<h4 class="coral-Heading">
AEM Asset selector lets you search for, filter, and browse assets within Adobe Experience Manager (AEM) Assets. Combined with Dynamic Media Smart Crop for Responsive images, you can select AEM assets in third party tools without the need to create manually cropped static renditions...
<br><br>
Enter the AEM URL eg. http://localhost:4502. Select Image profile, click on the button below to open AEM asset picker and select an asset...
<br><br>
<font color="red"> If you see a blank image the asset might not have been published in AEM or the asset was uploaded before applying a image processing profile to the folder in AEM</font>
</h4>
</div>
<div>
<div style="padding: 0 0 15px 0">
<input is="coral-textfield" type="text" value="http://localhost:4502"
id="aem-host-url" size="100" style="width: 30rem">
</div>
</div>
<div>
<label>Select Image Profile</label>

<div class="coral-RadioGroup" id="dyn-image-profiles-grp">
</div>
</div>
<div>
<label>Select a Dynamic Rendition</label>
<div class="coral-RadioGroup" id="dyn-renditions-grp">
</div>
</div>
<div>
<button is="coral-button" id="open-asset-picker" style="text-align: center" variant="primary">
Open Asset Selector
</button>
</div>
<div>
<h4 class="coral-Heading">Asset Info : </h4>

<div id="asset-info">No asset selected</div>

<img id="aem-image" src=""/>
</div>
</body>
</html>

11) Add the necessary JS code in eaem-asset-selector-dynamic-renditions\asset-selector\asset-selector.js to load the asset picker, select an asset and show the dynamic renditions

(function(){
var ASSET_SELECTOR_ID = "aem-asset-selector",
AUTH_TYPE_ASSET_SELECTOR = "authType=EAEM_ASSET_SELECTOR",
ASSET_SELECTOR_URL = "/aem/assetpicker",
IMAGE_PROFILES_URL = "/conf/global/settings/dam/adminui-extension/imageprofile.1.json",
assetsSelected, imageProfiles = {};

$(document).ready(initComponents);

function addHostAndAuthType(url){
if(!url || url.includes(AUTH_TYPE_ASSET_SELECTOR)){
return url;
}

var host = $("#aem-host-url").val();

if(!url.startsWith(host)){
url = $("#aem-host-url").val() + url;
}

if(url.includes("?")){
return (url + "&" + AUTH_TYPE_ASSET_SELECTOR);
}

return (url + "?" + AUTH_TYPE_ASSET_SELECTOR);
}

function loadImageProfiles(){
$("#dyn-image-profiles-grp").html("");

$("#dyn-renditions-grp").html("");

$.ajax(addHostAndAuthType(IMAGE_PROFILES_URL)).done(handler).fail(function(){
alert("Error getting image profile. Check AEM host value")
});

function handler(data){
if(!data["jcr:primaryType"]){
alert("Error getting image profiles");
return;
}

_.each(data, function(imgProfile, key){
if(key.startsWith("jcr:") || ( imgProfile["crop_type"] != "crop_smart" ) ){
return;
}

var crops = [];

_.each(imgProfile["banner"].split("|"), function(crop){
crops.push(crop.substring(0,crop.indexOf(",")));
});

imageProfiles[key] = crops;
});

addImageProfilesRadioGroup(imageProfiles);
}
}

function addImageProfilesRadioGroup(){
var html = "";

_.each(imageProfiles, function(crops, profileName){
html = html + '<coral-radio name="dyn-image-profiles" value="' + profileName + '">' + profileName + '</coral-radio>';
});

$("#dyn-image-profiles-grp").html(html);

$("[name='dyn-image-profiles']").change(addSmartCropsRadioGroup);
}

function addSmartCropsRadioGroup(){
var selProfile = $("input[name='dyn-image-profiles']:checked").val();

var html = '<coral-radio name="dyn-renditions" value="original" checked>Original</coral-radio>';

_.each(imageProfiles, function(crops, profileName){
if(profileName != selProfile){
return;
}

_.each(crops, function(crop){
html = html + '<coral-radio name="dyn-renditions" value="' + crop + '">' + crop + '</coral-radio>';
});
});

$("#dyn-renditions-grp").html(html);

$("[name='dyn-renditions']").change(setDynamicRenditionImage);

setDynamicRenditionImage();
}

function createDialog(){
var selUrl = $("#aem-host-url").val() + ASSET_SELECTOR_URL,
html = "<iframe width='1500px' height='800px' frameBorder='0' src='" + selUrl + "'></iframe>";

var dialog = new Coral.Dialog().set({
id: ASSET_SELECTOR_ID,
content: {
innerHTML: html
}
});

document.body.appendChild(dialog);
}

function hideDialog() {
var dialog = document.querySelector('#' + ASSET_SELECTOR_ID);

dialog.hide();
}

function showDialog(){
var dialog = document.querySelector('#' + ASSET_SELECTOR_ID);

dialog.show();

var $dialog = $(dialog);

adjustHeader($dialog);
}

function adjustHeader($dialog){
$dialog.find(".coral3-Dialog-header").remove().find(".coral3-Dialog-footer").remove();
}

function registerReceiveDataListener(handler) {
if (window.addEventListener) {
window.addEventListener("message", handler, false);
} else if (window.attachEvent) {
window.attachEvent("onmessage", handler);
}
}

function receiveMessage(event) {
var message = JSON.parse(event.data);

if(message.config.action == "close"){
hideDialog();
return;
}

assetsSelected = message.data;

setAssetInfo("original");

$("#aem-image").attr("src", assetsSelected[0].url);

$("input[name='dyn-renditions']")[0].checked = true;

var dialog = document.querySelector('#' + ASSET_SELECTOR_ID);

dialog.hide();
}

function setAssetInfo(dynRendition, dynRendUrl){
var assetInfo = "Path - " + assetsSelected[0].path;

if(dynRendition != "original"){
assetInfo = assetInfo + "<BR><BR>" + "Dynamic Media URL - <a href='" + dynRendUrl+ "' target='_blank'>" + dynRendUrl + "</a>";
}

$("#asset-info").html(assetInfo);
}

function setDynamicRenditionImage(){
var dynRendition = $("input[name='dyn-renditions']:checked").val(),
$aemImage = $("#aem-image");

if(!$aemImage.attr("src")){
return;
}

if(dynRendition == "original"){
$aemImage.attr("src", addHostAndAuthType(assetsSelected[0].url));

setAssetInfo(dynRendition);

return;
}

$.ajax(addHostAndAuthType(assetsSelected[0].url + ".2.json")).done(handler);

function handler(data){
if(!data["jcr:content"]){
alert("Error getting asset metadata");
return;
}

var metadata = data["jcr:content"]["metadata"],
dynRendUrl = metadata["dam:scene7Domain"] + "is/image/" + metadata["dam:scene7File"] + ":" + dynRendition;

$aemImage.attr("src", dynRendUrl);

setAssetInfo(dynRendition, dynRendUrl);
}
}

function initComponents(){
loadImageProfiles();

registerReceiveDataListener(receiveMessage);

$("#open-asset-picker").click(function(){
createDialog();

showDialog();
});

$("#aem-host-url").change(loadImageProfiles);
}
}());


12) The asset picker url /aem/assetpicker is loaded in an iFrame added in a Coral.Dialog. when user selects an asset, the picker fires message event, captured by the receiveMessage() function and selected assets specific data is extracted from event object...

13) The dynamic rendition of an asset in AEM asset details eg. http://localhost:4502/assetdetails.html/content/dam/experience-aem/room-green.jpg



14) The asset picker shown in an external site/page eg. file:///C:/dev/projects/eaem-extensions/eaem-65-extensions/eaem-asset-selector-dynamic-renditions/asset-selector/asset-selector.html



15) Selected AEM asset dynamic renditions shown in the external site / page


16) Test the image responsiveness using embed code provided in AEM asset details page and adding it in a standalone html


AEM 6540 - Assets Dynamic Media Show Specific Rendition (Smart Crop) by default on opening Asset Details

$
0
0

Goal


AEM Asset details page - /assetdetails.html remembers the last rail (Renditions or Timeline etc..) by storing the value in cookie rail-cq-propertiespage; if the requirement is to show a specific rail on open eg. Renditions, further show a specific rendition eg. customer specific dynamic media Smart Crop Destination-Storycard-1305x555, try the following extension...

Demo | Package Install | Github



Solution


1) Create a filter apps.experienceaem.assets.EAEMSetDefaultRailCookie to set the cookie rail-cq-propertiespage value to renditions every time it's accessed...

package apps.experienceaem.assets;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import java.io.IOException;

@Component(
service = Filter.class,
immediate = true,
name = "Experience AEM Default Rail Cookie Filter",
property = {
Constants.SERVICE_RANKING + ":Integer=-99",
"sling.filter.scope=COMPONENT"
}
)
public class EAEMSetDefaultRailCookie implements Filter {
public static String RAIL_CQ_PROPERTIES_PAGE = "rail-cq-propertiespage";

public static String RENDITIONS = "renditions";

public void init(FilterConfig filterConfig) throws ServletException {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

String requestURI = slingRequest.getRequestURI();

if (!requestURI.startsWith("/assetdetails.html")) {
chain.doFilter(request, response);
return;
}

SlingHttpServletRequest defaultRailCookie = new EAEMDefaultRailServletRequestWrapper(slingRequest);
chain.doFilter(defaultRailCookie, response);
}

public void destroy() {
}

private class EAEMDefaultRailServletRequestWrapper extends SlingHttpServletRequestWrapper {
public EAEMDefaultRailServletRequestWrapper(final SlingHttpServletRequest request) {
super(request);
}

@Override
public Cookie getCookie(String cookieName) {
if (!EAEMSetDefaultRailCookie.RAIL_CQ_PROPERTIES_PAGE.equals(cookieName)) {
return super.getCookie(cookieName);
}

Cookie cookie = new Cookie(EAEMSetDefaultRailCookie.RAIL_CQ_PROPERTIES_PAGE, EAEMSetDefaultRailCookie.RENDITIONS);
cookie.setPath("/");

return cookie;
}
}

}


2) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-default-specific-rendition-asset-details

3) Create node /apps/eaem-default-specific-rendition-asset-details/clientlib of type cq:ClientLibraryFolder, add String[] property categories with value [dam.gui.smartcroprendition.list], String[] property dependencies with value lodash.

4) Create file (nt:file) /apps/eaem-default-specific-rendition-asset-details/clientlib/js.txt, add

                        show-default-rendition.js

5) Create file (nt:file) /apps/eaem-default-specific-rendition-asset-details/clientlib/show-default-rendition.js, add the following code

(function ($, $document) {
var DEFAULT_RENDITION = "Destination-Storycard-1305x555", initialized = false;

$document.on("foundation-contentloaded", showDefaultRendition);

function showDefaultRendition(){
if(!isAssetDetailsPage() || initialized){
return;
}

var fileName = window.location.pathname;

if(_.isEmpty(fileName)){
return;
}

$('.smartcrop-renditions .each-rendition').each(function(e){
initialized = true;

var $smartRend = $(this), rendName;

if ($smartRend.data("assetNotProcessed")){
return;
}

rendName = $smartRend.find(".col1presetname").html();

if(rendName.trim() !== DEFAULT_RENDITION){
return;
}

$smartRend.click();
});
}

function isAssetDetailsPage(){
return window.location.pathname.startsWith("/assetdetails.html");
}
}(jQuery, jQuery(document)));

AEM 6540 - Assets Dynamic Media Standalone Script to search Assets in Scene7

$
0
0

Goal


You have a AEM Dynamic Media instance and the requirement is to get list of all Image Sets in associated Scene7 account. The following standalone java program explores using the Scene7 wsdl IpsAPI to connect, search and fetch Image Sets

Github

Solution


1) Login to AEM CrxDe - http://localhost:4502/crx/de and get the Scene7 Company handle used in Dynamic Media configuration eg. /conf/global/settings/cloudconfigs/dmscene7/jcr:content



2) Open following AEM config page and from the list of scene7 endpoints get the one for your region (available in AEM DM configuration, step 1 above eg. northamerica-enterprise)

                     http://localhost:4502/libs/settings/dam/scene7/endpoints.html



3) Get the Scene7 IpsAPI wsdl SDK from AEM installation launchpad, by looking for the bundle number folder, copy to your program classpath folder and rename bundle.jar to versioned jar name eg. cq-scene7-wsdl-1.3.2.jar



4) In your standalone program eg. SearchScene7Assets classpath add the following jars (available in Adobe Repo - https://repo.adobe.com/nexus/content/groups/public/)

                            C:\dev\projects\templibs\scene7\cq-scene7-wsdl-1.3.2.jar
                            C:\Users\<user>\.m2\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar
                            C:\Users\<user>\.m2\repository\commons-httpclient\commons-httpclient\3.1\commons-httpclient-3.1.jar
                            C:\Users\<user>\.m2\repository\commons-logging\commons-logging-api\1.1\commons-logging-api-1.1.jar 

    5) Add the following logic in standalone program SearchScene7Assets to connect to Scene7, get and parse search response

    import com.scene7.ipsapi.AuthHeader;
    import com.scene7.ipsapi.SearchAssetsParam;
    import com.scene7.ipsapi.StringArray;
    import org.apache.commons.httpclient.HttpClient;
    import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
    import org.apache.commons.httpclient.methods.PostMethod;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;

    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Marshaller;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.xpath.XPath;
    import javax.xml.xpath.XPathConstants;
    import javax.xml.xpath.XPathFactory;
    import java.io.ByteArrayInputStream;
    import java.io.StringWriter;
    import java.util.HashMap;
    import java.util.Map;

    public class SearchScene7Assets {
    private static String S7_NA_IPS_URL = "https://s7sps1apissl.scene7.com/scene7/api/IpsApiService"; //available in AEM http://localhost:4502/libs/settings/dam/scene7/endpoints.html
    private static String S7_COMPANY_HANDLE = "c|230095"; // available via api or in AEM /conf/global/settings/cloudconfigs/dmscene7/jcr:content
    private static String S7_USER = "";
    private static String S7_PASS = "";
    private static String STAND_ALONE_APP_NAME = "Experiencing AEM";

    public static void main(String[] args) throws Exception {
    AuthHeader authHeader = getS7AuthHeader();

    Marshaller marshaller = getMarshaller(AuthHeader.class);
    StringWriter sw = new StringWriter();
    marshaller.marshal(authHeader, sw);

    String authHeaderStr = sw.toString();

    SearchAssetsParam searchAssetsParam = getSearchAssetsParam();

    marshaller = getMarshaller(searchAssetsParam.getClass());
    sw = new StringWriter();
    marshaller.marshal(searchAssetsParam, sw);

    String apiMethod = sw.toString();

    byte[] responseBody = getResponse(authHeaderStr, apiMethod);

    Map<String, String> imageSets = parseResponse(responseBody);

    printImageSets(imageSets);
    }

    private static void printImageSets(Map<String, String> imageSets){
    if(imageSets.isEmpty()){
    System.out.println("No imagesets in account - " + S7_COMPANY_HANDLE);
    return;
    }

    System.out.println("Image Sets : ");

    for(Map.Entry entry : imageSets.entrySet()){
    System.out.println("" + entry.getKey() + " - " + entry.getValue());
    }
    }

    private static Map<String, String> parseResponse(byte[] responseBody) throws Exception{
    Map<String, String> imageSets = new HashMap<String, String>();

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

    DocumentBuilder builder = factory.newDocumentBuilder();

    ByteArrayInputStream input = new ByteArrayInputStream(responseBody);

    Document doc = builder.parse(input);

    XPath xPath = XPathFactory.newInstance().newXPath();

    String expression = "/searchAssetsReturn/assetArray/items";

    NodeList itemList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);

    for (int i = 0; i < itemList.getLength(); i++) {
    Node item = itemList.item(i);

    if(item.getNodeType() == Node.ELEMENT_NODE) {
    Element eElement = (Element) item;

    String handle = eElement.getElementsByTagName("assetHandle").item(0).getTextContent();
    String name = eElement.getElementsByTagName("name").item(0).getTextContent();

    imageSets.put(handle, name);
    }
    }

    return imageSets;
    }

    private static byte[] getResponse(String authHeaderStr, String apiMethod) throws Exception{
    StringBuilder requestBody = new StringBuilder("<Request xmlns=\"http://www.scene7.com/IpsApi/xsd/2017-10-29-beta\">");
    requestBody.append(authHeaderStr).append(apiMethod).append("</Request>");

    byte[] responseBody = null;
    PostMethod postMethod = null;

    try {
    HttpClient httpclient = new HttpClient();

    postMethod = new PostMethod(S7_NA_IPS_URL);

    postMethod.setRequestHeader("Content-Type", "text/xml");
    postMethod.setRequestEntity(new ByteArrayRequestEntity(requestBody.toString().getBytes()));

    int responseCode = httpclient.executeMethod(postMethod);

    if(responseCode != 200){
    System.out.println("Response code - " + responseCode + ", returning here...");
    }

    responseBody = postMethod.getResponseBody();
    }finally{
    if(postMethod != null){
    postMethod.releaseConnection();
    }
    }

    return responseBody;
    }

    private static AuthHeader getS7AuthHeader(){
    AuthHeader authHeader = new AuthHeader();

    authHeader.setUser(S7_USER);
    authHeader.setPassword(S7_PASS);
    authHeader.setAppName(STAND_ALONE_APP_NAME);
    authHeader.setAppVersion("1.0");
    authHeader.setFaultHttpStatusCode(new Integer(200));

    return authHeader;
    }

    private static SearchAssetsParam getSearchAssetsParam(){
    SearchAssetsParam searchAssetsParam = new SearchAssetsParam();
    StringArray assetTypes = new StringArray();
    assetTypes.getItems().add("ImageSet");

    searchAssetsParam.setCompanyHandle(S7_COMPANY_HANDLE);
    searchAssetsParam.setIncludeSubfolders(true);
    searchAssetsParam.setAssetTypeArray(assetTypes);

    return searchAssetsParam;
    }

    private static Marshaller getMarshaller(Class apiMethodClass) throws JAXBException {
    Marshaller marshaller = JAXBContext.newInstance(new Class[]{apiMethodClass}).createMarshaller();
    marshaller.setProperty("jaxb.formatted.output", Boolean.valueOf(true));
    marshaller.setProperty("jaxb.fragment", Boolean.valueOf(true));
    return marshaller;
    }
    }


    6) Sample output running the program from a local JVM...



    7) Same XML response, executing Scene7 API call...


    8) Searching for ImageSets in Scene7 Interface...




    AEM 6540 - Creating Dynamic Media Classic (Scene7) Templates for Image Text Overlays

    $
    0
    0

    The following steps explain creating a Scene7 Template using layered PSD file for adding text overlays on images. Image Server (IPS) can deliver these images using presets with text and image passed as parameters in URL. 

    More about templates in documentation



    Create PS Document

    1) Open Photoshop, Click File> New, enter Width & Height of image (e.g. 450 x 450), select Transparent background, click Create...




    2) Add a template image on the transparent background. You can open the image ( File > Open ) adjust size to 450 X 450 and copy it to the psd layer created in step 1...




    3) Add a Rectangle on the Image (about 20% from the top), fill with gray background and set the opacity to say 75%. This section on the image is for text overlay...




    4) Rename the layers so the generated assets from PSD layers in Scene7 have some meaningful asset names. Download the sample psd here



    Create Scene7 Template

    5) Login to Scene7 SPS - https://s7sps1.scene7.com/IpsWeb/, select the folder in left pane and click Upload... 




    6) Select the PSD created above by clinking Browse; In the bottom right corner of upload window, click Job Options to configure the layer extract (and generate assets) specific settings...






    7) When the PSD upload job processing is complete in Scene7, you should see the generated assets from extracted layers... (if you donot see generated content, click Setup> General Settings > Show Generated Content)






    8) Click Edit on the Template card (this template was created from PS doc uploaded). Edit it to add heading & subtext overlays and parameterize the template....


    9) Drag the Text tool onto image, using the RTF editor add some text e.g. HEADING, format it with font, size etc. 




    10) Similarly add the subtext...




    11) Before parameterizing the template, check if Preview looks ok (image with heading and subtext overlay)



    Create Scene7 Template Parameters

    12) Lets parameterize the image, heading and subtext of the template for creating dynamic images with text overlays (pass the image name, heading and subtext as parameters to image url). For adding Image parameter, click on P button




    13) For parameterizing the heading text, click on the P button next to it, click on the Text tab in resulting window, select the text and click add to configure the url parameter



    14) Similarly parameterize the subtext



    15) Click Preview, to experiment with the parameters, change text, image etc... copy url and open in new browser tab, check the image rendered....



    16) Create a custom preset EAEM, for 450 x 450 sizing and 100% quality....



    17) Image delivery using custom preset EAEM





    AEM Cloud Service - Tailing AEM Environment Logs in a Cloud Manager Program

    $
    0
    0

    For debugging any issue in AEM the ideal way is to setup a JVM remote debugger, however if that is not an option you could try log tailing....

    Following steps explain the setup and tailing an environment log in a cloud manager program using Cloud Manager Plugin for the Adobe I/O CLI. Check it on Github




    Install the Adobe IO Node.js Application


    1) Open command prompt and install Adobe I/O CLI. More details on Adobe IO CLI here

    npm install -g @adobe/aio-cli

    2) Install the Cloud Manager Plugin. More details on the CM plugin here

    aio plugins:install @adobe/aio-cli-plugin-cloudmanager


    Setup Service Account Connection (using JWT)


    3) Accessing any Adobe product API (here Cloud Manger in Experience Cloud) is achieved using a service account creation in Adobe IO (https://console.adobe.io/). Assuming you have System Administrator or Developer role in the Org (eg. ORG ID: 2FBC7_PURPOSEFUL_OBFUSCATION_95FBB@AdobeOrg) create a project and click on the Add API button (for more details on service accounts integration check documentation)



    4) Select Cloud Manager under Experience Cloud



    5) Generate a Key pair (private, public key) to exchange access tokens...




    6) Public key remains with Adobe IO and the Private key is configured in your application (here Adobe IO command line application)



    7) Select the product profiles (features) of Cloud Manager you'd like to use in the CLI application. Your integration's service account will gain access to granular features of the Adobe product based on the product profiles that you select.



    8) Make note of the Credentials (for using later in the configuration), click on Generate JWT tab to copy the JWT payload






    9) Create a file config.json, copy the client_id, client_secret, jwt_payload values to the config file, for e.g.

    {
    "client_id": "bef8a_PURPOSEFUL_OBFUSCATION_2ea5",
    "client_secret": "fdcc_PURPOSEFUL_OBFUSCATION_e46d4",
    "jwt_payload": {"exp":1589039706,"iss":"2FB_PURPOSEFUL_OBFUSCATION_5FBB@AdobeOrg","sub":"6BC_PURPOSEFUL_OBFUSCATION_FA7@techacct.adobe.com","https://ims-na1.adobelogin.com/s/ent_cloudmgr_sdk":true,"aud":"https://ims-na1.adobelogin.com/c/be_PURPOSEFUL_OBFUSCATION_ea5"},
    "token_exchange_url": "https://ims-na1.adobelogin.com/ims/exchange/jwt"
    }


    Configure Adobe IO CLI


    10) Open command prompt and set the config file in Adobe IO app, for eg.

    aio config:set jwt-auth ./config.json --file --json



    11) Set the private key downloaded in step 6, for eg.

    aio config:set jwt-auth.jwt_private_key ./private.key --file



    12) Set the cloud manager program id, if you want to avoid passing the program ID flag repeatedly (something like a default program) for eg.

    aio config:set cloudmanager_programid 10961





    13) Get the services names in an environment of the program, for e.g.

    aio cloudmanager:list-available-log-options 19892



    14) To tail the error log use aio cloudmanager:tail-log ENVIRONMENTID SERVICE NAME, for e.g.

    aio cloudmanager:tail-log 19892 author aemerror



    AEM 6540 - Core Components 280 - Touch UI RTE (Rich Text Editor) Dialog Emojis Plugin

    $
    0
    0

    Touch UI Emoji Picker Plugin for RTE (Rich Text Editor) Dialog - /libs/cq/gui/components/authoring/dialog/richtext

    In Core Components 2.8.0uiSettings/cui/dialogFullScreen was removed and the plugin settings were moved to Template (Component) Policies, this post is for core components >= 2.8.0 

    Uses Open Emoji API https://emoji-api.com/

    For demo purposes, design dialog of core text component v2 was modified to add the emoji picker configuration - /apps/core/wcm/components/text/v2/text/cq:design_dialog/content/items/tabs/items/plugins/items/experience-aem-fonts



    Emoji Plugin



    Plugin Configuration in Design





    Enable Plugin in Component Policy



    Let's Do It

    1) Login to CRXDE Lite, add nt:folder /apps/eaem-emojis-rte-plugin

    2) To show the Emoji Picker in a dialog create cq:Page /apps/eaem-emojis-rte-plugin/emoji-selector and add  eaem.rte.emojis.plugincategories in /apps/eaem-emojis-rte-plugin/emoji-selector/jcr:content/head/clientlibs

    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:Page">
    <jcr:content
    jcr:mixinTypes="[sling:VanityPath]"
    jcr:primaryType="nt:unstructured"
    jcr:title="EAEM Emoji Selector"
    sling:resourceType="granite/ui/components/coral/foundation/page">
    <head jcr:primaryType="nt:unstructured">
    <favicon
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
    <viewport
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
    <clientlibs
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
    categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral,eaem.rte.emojis.plugin]"/>
    </head>
    <body
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/page/body">
    <items jcr:primaryType="nt:unstructured">
    <form
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form"
    class="foundation-form content-container"
    maximized="{Boolean}true"
    style="vertical">
    <items jcr:primaryType="nt:unstructured">
    <emoji
    jcr:primaryType="nt:unstructured"
    jcr:title="Click on the Emoji select.."
    sling:resourceType="granite/ui/components/coral/foundation/container">
    <items jcr:primaryType="nt:unstructured">
    <fixedColumns
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
    margin="{Boolean}true">
    <items jcr:primaryType="nt:unstructured">
    <column
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/container">
    <items jcr:primaryType="nt:unstructured">
    <emojiPicker
    jcr:primaryType="nt:unstructured"
    sling:resourceType="/apps/eaem-emojis-rte-plugin/emoji-widget"
    fieldDescription="Select Emoji..."
    fieldLabel="Select Emoji..."
    name="./eaemEmoji"/>
    </items>
    </column>
    </items>
    </fixedColumns>
    </items>
    </emoji>
    </items>
    </form>
    </items>
    </body>
    </jcr:content>
    </jcr:root>

    3) Create widget /apps/eaem-emojis-rte-plugin/emoji-widget/emoji-widget.jsp to add a div container, search code for the emojis...

    <%@ include file="/libs/granite/ui/global.jsp" %>

    <ui:includeClientLib categories="lodash" />

    <%
    String defaultKeyword = "face";
    %>

    <div class="coral-Form-fieldwrapper">
    <label class="coral-Form-fieldlabel">Search</label>
    <input is="coral-textfield" class="coral-Form-field" placeholder="<%= defaultKeyword %>"
    id="eaem-emoji-input"
    value="<%= defaultKeyword %>">
    </div>

    <div class="coral-Form-fieldwrapper" id="eaem-emojis">
    </div>

    <script>
    (function($){
    var URL = "https://emoji-api.com/emojis?access_key=bcbd6980f7392ceda8914932404927e9b198486e&search=";

    function showEmojis(keyword) {
    $.ajax(URL + keyword).done(handler);

    function handler(data) {
    var $emojis = $("#eaem-emojis"), html = "";

    _.each(data, function (emoji) {
    html = html + getEmojiHtml(emoji["character"]);
    });

    $emojis.html(html);
    }

    function getEmojiHtml(character) {
    return "<span style='cursor:pointer'>" + character + "</span>";
    }
    }

    function addListener(){
    $("form").on("submit", function(){
    showEmojis($("#eaem-emoji-input").val());
    return false;
    });
    }

    showEmojis("<%=defaultKeyword%>");

    addListener();
    }(jQuery));
    </script>

    4) Create clientlib (cq:ClientLibraryFolder) /apps/eaem-emojis-rte-plugin/clientlib set property categories to [cq.authoring.dialog.all, eaem.rte.emojis.plugin] and dependencies to [lodash]

    5) Create file (nt:file) /apps/eaem-emojis-rte-plugin/clientlib/js.txt, add the following content

                                        eaem-emojis.js

    6) Create file (nt:file) /apps/eaem-emojis-rte-plugin/clientlib/eaem-emojis.js, add the following code

    (function($, CUI, $document){
    var GROUP = "experience-aem-emojis",
    INSERT_EMOJI_FEATURE = "insertEmoji",
    EAEM_INSERT_EMOJI_DIALOG = "eaemTouchUIInsertEmojiDialog",
    SENDER = "experience-aem", REQUESTER = "requester",
    FONT_SELECTOR_URL = "/apps/eaem-emojis-rte-plugin/emoji-selector.html",
    url = document.location.pathname;

    if( url.indexOf(FONT_SELECTOR_URL) == 0 ){
    handlePicker();
    return;
    }

    function handlePicker(){
    $document.on("click", "#eaem-emojis span", addEmojiSelectListener);
    }

    function addEmojiSelectListener(){
    var message = {
    sender: SENDER,
    action: "submit",
    data: {
    emoji: $(this).html()
    }
    };

    getParent().postMessage(JSON.stringify(message), "*");
    }

    function getParent() {
    if (window.opener) {
    return window.opener;
    }

    return parent;
    }

    addPlugin();

    addPluginToDefaultUISettings();

    addDialogTemplate();

    function addDialogTemplate(){
    var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;

    var html = "<iframe width='700px' height='300px' frameBorder='0' src='" + url + "'></iframe>";

    if(_.isUndefined(CUI.rte.Templates)){
    CUI.rte.Templates = {};
    }

    if(_.isUndefined(CUI.rte.templates)){
    CUI.rte.templates = {};
    }

    CUI.rte.templates['dlg-' + EAEM_INSERT_EMOJI_DIALOG] = CUI.rte.Templates['dlg-' + EAEM_INSERT_EMOJI_DIALOG] = Handlebars.compile(html);
    }

    function addPluginToDefaultUISettings(){
    var groupFeature = GROUP + "#" + INSERT_EMOJI_FEATURE,
    toolbar = CUI.rte.ui.cui.DEFAULT_UI_SETTINGS.dialogFullScreen.toolbar;

    if(toolbar.includes(groupFeature)){
    return;
    }

    toolbar.splice(3, 0, groupFeature);
    }

    var EAEMInsertEmojiDialog = new Class({
    extend: CUI.rte.ui.cui.AbstractDialog,

    toString: "EAEMInsertEmojiDialog",

    initialize: function(config) {
    this.exec = config.execute;
    },

    getDataType: function() {
    return EAEM_INSERT_EMOJI_DIALOG;
    }
    });

    function addPlugin(){
    var EAEMTouchUIInsertEmojiPlugin = new Class({
    toString: "EAEMTouchUIInsertEmojiPlugin",

    extend: CUI.rte.plugins.Plugin,

    pickerUI: null,

    getFeatures: function() {
    return [ INSERT_EMOJI_FEATURE ];
    },

    initializeUI: function(tbGenerator) {
    var plg = CUI.rte.plugins;

    addPluginToDefaultUISettings();

    if (!this.isFeatureEnabled(INSERT_EMOJI_FEATURE)) {
    return;
    }

    this.pickerUI = tbGenerator.createElement(INSERT_EMOJI_FEATURE, this, false, { title: "Select Emoji..." });
    tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 10);

    var groupFeature = GROUP + "#" + INSERT_EMOJI_FEATURE;
    tbGenerator.registerIcon(groupFeature, "heart");
    },

    execute: function (pluginCommand, value, envOptions) {
    var context = envOptions.editContext,
    ek = this.editorKernel;

    if (pluginCommand != INSERT_EMOJI_FEATURE) {
    return;
    }

    var dialog,dm = ek.getDialogManager(),
    $container = CUI.rte.UIUtils.getUIContainer($(context.root)),
    propConfig = {
    'parameters': {
    'command': this.pluginId + '#' + INSERT_EMOJI_FEATURE
    }
    };

    if(this.eaemInsertEmojiDialog){
    dialog = this.eaemInsertEmojiDialog;

    dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl());
    }else{
    dialog = new EAEMInsertEmojiDialog();

    dialog.attach(propConfig, $container, this.editorKernel);

    dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl());

    this.eaemInsertEmojiDialog = dialog;

    $(window).off('message', receiveMessage).on('message', receiveMessage);
    }

    dm.show(dialog);

    function receiveMessage(event) {
    event = event.originalEvent || {};

    if (_.isEmpty(event.data)) {
    return;
    }

    var message, action;

    try{
    message = JSON.parse(event.data);
    }catch(err){
    return;
    }

    if (!message || message.sender !== SENDER) {
    return;
    }

    action = message.action;

    if(action === "submit"){
    ek.relayCmd(pluginCommand, message.data);
    }

    dialog.hide();
    }
    },

    getPickerIFrameUrl: function(){
    return Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;
    },

    updateState: function(selDef) {
    var hasUC = this.editorKernel.queryState(INSERT_EMOJI_FEATURE, selDef);

    if (this.pickerUI != null) {
    this.pickerUI.setSelected(hasUC);
    }
    }
    });

    var EAEMTouchUIEmojiCmd = new Class({
    toString: "EAEMTouchUIEmojiCmd",

    extend: CUI.rte.commands.Command,

    isCommand: function (cmdStr) {
    return (cmdStr.toLowerCase() == INSERT_EMOJI_FEATURE);
    },

    getProcessingOptions: function () {
    var cmd = CUI.rte.commands.Command;
    return cmd.PO_SELECTION | cmd.PO_BOOKMARK | cmd.PO_NODELIST;
    },

    execute: function (execDef) {
    var emoji = execDef.value.emoji, context = execDef.editContext,
    emojiSpan = context.doc.createElement("span");

    emojiSpan.innerHTML = emoji;

    var range = CUI.rte.Common.ua.isIE ? CUI.rte.Selection.saveNativeSelection(context)
    : CUI.rte.Selection.getLeadRange(context);

    range.insertNode(emojiSpan);
    },

    queryState: function(selectionDef, cmd) {
    return false;
    }
    });

    CUI.rte.commands.CommandRegistry.register(INSERT_EMOJI_FEATURE, EAEMTouchUIEmojiCmd);

    CUI.rte.plugins.PluginRegistry.register(GROUP,EAEMTouchUIInsertEmojiPlugin);
    }
    }(jQuery, window.CUI,jQuery(document)));


    AEM 6540 - Assets Dynamic Media Standalone Script to browse Assets in Scene7

    $
    0
    0

    Goal


    You have a AEM Dynamic Media instance and the requirement is to browse and get list of all folders in associated Scene7 account. The following standalone java program explores using the Scene7 wsdl IpsAPI to connect and browse....

    For searching check this post

    Github

    Solution


    1) Login to AEM CrxDe - http://localhost:4502/crx/de and get the Scene7 Company handle used in Dynamic Media configuration eg. /conf/global/settings/cloudconfigs/dmscene7/jcr:content



    2) Open following AEM config page and from the list of scene7 endpoints get the one for your region (available in AEM DM configuration, step 1 above eg. northamerica-enterprise)

                         http://localhost:4502/libs/settings/dam/scene7/endpoints.html



    3) Get the Scene7 IpsAPI wsdl SDK from AEM installation launchpad, by looking for the bundle number folder, copy to your program classpath folder and rename bundle.jar to versioned jar name eg. cq-scene7-wsdl-1.3.2.jar



    4) In your standalone program eg. BrowseScene7Assets classpath add the following jars (available in Adobe Repo - https://repo.adobe.com/nexus/content/groups/public/)

                                C:\dev\projects\templibs\scene7\cq-scene7-wsdl-1.3.2.jar
                                C:\Users\<user>\.m2\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar
                                C:\Users\<user>\.m2\repository\commons-httpclient\commons-httpclient\3.1\commons-httpclient-3.1.jar
                                C:\Users\<user>\.m2\repository\commons-logging\commons-logging-api\1.1\commons-logging-api-1.1.jar 

      5) Add the following logic in standalone program BrowseScene7Assets to connect to Scene7, get and parse response....

      import com.scene7.ipsapi.*;
      import org.apache.commons.httpclient.HttpClient;
      import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
      import org.apache.commons.httpclient.methods.PostMethod;
      import org.w3c.dom.Document;
      import org.w3c.dom.Element;
      import org.w3c.dom.Node;
      import org.w3c.dom.NodeList;

      import javax.xml.bind.JAXBContext;
      import javax.xml.bind.JAXBException;
      import javax.xml.bind.Marshaller;
      import javax.xml.parsers.DocumentBuilder;
      import javax.xml.parsers.DocumentBuilderFactory;
      import javax.xml.xpath.XPath;
      import javax.xml.xpath.XPathConstants;
      import javax.xml.xpath.XPathFactory;
      import java.io.ByteArrayInputStream;
      import java.io.StringWriter;
      import java.util.ArrayList;
      import java.util.List;

      public class BrowseScene7Assets {
      private static String S7_NA_IPS_URL = "https://s7sps1apissl.scene7.com/scene7/api/IpsApiService"; //available in AEM http://localhost:4502/libs/settings/dam/scene7/endpoints.html
      private static String S7_COMPANY_HANDLE = "c|9686"; //c|230095 available via api or in AEM /conf/global/settings/cloudconfigs/dmscene7/jcr:content
      private static String S7_USER = "";
      private static String S7_PASS = "";
      private static String STAND_ALONE_APP_NAME = "Experiencing AEM";
      private static String FOLDER_HANDLE = ""; //Leave empty to browse root
      private static int folderCount = 0;

      public static void main(String[] args) throws Exception {
      browseFolder(null);
      System.out.println("TOTAL FOLDERS : " + folderCount);
      }

      private static void browseFolder(S7Folder s7Folder) throws Exception {
      AuthHeader authHeader = getS7AuthHeader();

      Marshaller marshaller = getMarshaller(AuthHeader.class);
      StringWriter sw = new StringWriter();
      marshaller.marshal(authHeader, sw);

      String authHeaderStr = sw.toString();

      GetFolderTreeParam getFolderTreeParam = getGetFolderTreeParam(s7Folder);

      marshaller = getMarshaller(getFolderTreeParam.getClass());
      sw = new StringWriter();
      marshaller.marshal(getFolderTreeParam, sw);

      String apiMethod = sw.toString();

      byte[] responseBody = getResponse(authHeaderStr, apiMethod);

      List<S7Folder> folders = parseResponse(responseBody, s7Folder);

      folders.forEach(folder -> {
      if(folder.isHasSubFolders()){
      try{
      browseFolder(folder);
      }catch (Exception e){
      e.printStackTrace();
      }
      }
      });

      printFolders(s7Folder, folders);
      }

      private static void printFolders(S7Folder parentFolder, List<S7Folder> folders){
      if( ( parentFolder == null ) && folders.isEmpty()){
      System.out.println("No folders in account - " + S7_COMPANY_HANDLE);
      return;
      }

      if(folders.isEmpty()){
      System.out.println("No folders under - " + parentFolder.getFolderPath());
      return;
      }

      if(parentFolder == null){
      System.out.println(S7_COMPANY_HANDLE);
      }else{
      System.out.println(parentFolder.getFolderPath());
      }

      folders.forEach(folder -> {
      folderCount++;
      System.out.println("\t" + folder.getFolderPath());
      });
      }

      private static List<S7Folder> parseResponse(byte[] responseBody, S7Folder parentFolder) throws Exception{
      List<S7Folder> folders = new ArrayList<S7Folder>();

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

      DocumentBuilder builder = factory.newDocumentBuilder();

      ByteArrayInputStream input = new ByteArrayInputStream(responseBody);

      Document doc = builder.parse(input);

      XPath xPath = XPathFactory.newInstance().newXPath();

      String expression = "/getFolderTreeReturn/folders";

      if(parentFolder != null){
      expression = "/getFolderTreeReturn/folders/subfolderArray";
      }

      NodeList itemList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);

      for (int i = 0; i < itemList.getLength(); i++) {
      Node item = itemList.item(i);

      if(parentFolder != null){
      NodeList childList = item.getChildNodes();

      for (int j = 0; j < childList.getLength(); j++) {
      folders.add(fillFolderDetail(childList.item(j)));
      }
      }else{
      folders.add(fillFolderDetail(item));
      }
      }

      return folders;
      }

      private static S7Folder fillFolderDetail(Node item){
      S7Folder s7Folder = new S7Folder();

      if(item.getNodeType() == Node.ELEMENT_NODE) {
      Element eElement = (Element) item;

      s7Folder.setFolderHandle(eElement.getElementsByTagName("folderHandle").item(0).getTextContent());
      s7Folder.setFolderPath(eElement.getElementsByTagName("path").item(0).getTextContent());
      s7Folder.setHasSubFolders(new Boolean(eElement.getElementsByTagName("hasSubfolders").item(0).getTextContent()));
      }

      return s7Folder;
      }

      private static byte[] getResponse(String authHeaderStr, String apiMethod) throws Exception{
      StringBuilder requestBody = new StringBuilder("<Request xmlns=\"http://www.scene7.com/IpsApi/xsd/2017-10-29-beta\">");
      requestBody.append(authHeaderStr).append(apiMethod).append("</Request>");

      byte[] responseBody = null;
      PostMethod postMethod = null;

      try {
      HttpClient httpclient = new HttpClient();

      postMethod = new PostMethod(S7_NA_IPS_URL);

      postMethod.setRequestHeader("Content-Type", "text/xml");
      postMethod.setRequestEntity(new ByteArrayRequestEntity(requestBody.toString().getBytes()));

      int responseCode = httpclient.executeMethod(postMethod);

      if(responseCode != 200){
      System.out.println("Response code - " + responseCode + ", returning here...");
      }

      responseBody = postMethod.getResponseBody();
      }finally{
      if(postMethod != null){
      postMethod.releaseConnection();
      }
      }

      return responseBody;
      }

      private static AuthHeader getS7AuthHeader(){
      AuthHeader authHeader = new AuthHeader();

      authHeader.setUser(S7_USER);
      authHeader.setPassword(S7_PASS);
      authHeader.setAppName(STAND_ALONE_APP_NAME);
      authHeader.setAppVersion("1.0");
      authHeader.setFaultHttpStatusCode(new Integer(200));

      return authHeader;
      }

      private static GetFolderTreeParam getGetFolderTreeParam(S7Folder s7Folder){
      GetFolderTreeParam getFolderTreeParam = new GetFolderTreeParam();

      getFolderTreeParam.setCompanyHandle(S7_COMPANY_HANDLE);

      getFolderTreeParam.setDepth(1);

      if(s7Folder != null){
      getFolderTreeParam.setFolderPath(s7Folder.getFolderPath());
      }

      return getFolderTreeParam;
      }

      private static Marshaller getMarshaller(Class apiMethodClass) throws JAXBException {
      Marshaller marshaller = JAXBContext.newInstance(new Class[]{apiMethodClass}).createMarshaller();
      marshaller.setProperty("jaxb.formatted.output", Boolean.valueOf(true));
      marshaller.setProperty("jaxb.fragment", Boolean.valueOf(true));
      return marshaller;
      }

      private static class S7Folder{
      private String folderHandle;
      private String folderPath;
      private boolean hasSubFolders;

      public String getFolderHandle() {
      return folderHandle;
      }

      public void setFolderHandle(String folderHandle) {
      this.folderHandle = folderHandle;
      }

      public String getFolderPath() {
      return folderPath;
      }

      public void setFolderPath(String folderPath) {
      this.folderPath = folderPath;
      }

      public boolean isHasSubFolders() {
      return hasSubFolders;
      }

      public void setHasSubFolders(boolean hasSubFolders) {
      this.hasSubFolders = hasSubFolders;
      }

      public String toString(){
      return "[" + folderHandle + "," + folderPath + "," + hasSubFolders + "]";
      }
      }
      }


      6) Sample output running the program from a local JVM...





      AEM 6540 - Assets Dynamic Media Standalone Script to list Assets by Folder in Scene7

      $
      0
      0

      Goal


      You have a AEM Dynamic Media instance and the requirement is to browse and get list of assets grouped by folder in associated Scene7 account. The following standalone java program explores using the Scene7 wsdl IpsAPI to connect and browse, list assets by folder....

      For searching check this post

      Github

      Solution


      1) Login to AEM CrxDe - http://localhost:4502/crx/de and get the Scene7 Company handle used in Dynamic Media configuration eg. /conf/global/settings/cloudconfigs/dmscene7/jcr:content



      2) Open following AEM config page and from the list of scene7 endpoints get the one for your region (available in AEM DM configuration, step 1 above eg. northamerica-enterprise)

                           http://localhost:4502/libs/settings/dam/scene7/endpoints.html



      3) Get the Scene7 IpsAPI wsdl SDK from AEM installation launchpad, by looking for the bundle number folder, copy to your program classpath folder and rename bundle.jar to versioned jar name eg. cq-scene7-wsdl-1.3.2.jar



      4) In your standalone program eg. BrowseScene7AssetsByFolder classpath add the following jars (available in Adobe Repo - https://repo.adobe.com/nexus/content/groups/public/)

                                  C:\dev\projects\templibs\scene7\cq-scene7-wsdl-1.3.2.jar
                                  C:\Users\<user>\.m2\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar
                                  C:\Users\<user>\.m2\repository\commons-httpclient\commons-httpclient\3.1\commons-httpclient-3.1.jar
                                  C:\Users\<user>\.m2\repository\commons-logging\commons-logging-api\1.1\commons-logging-api-1.1.jar 
                                  C:\Users\<user>\.m2\repository\commons-io\commons-io\2.5\commons-io-2.5.jar 

        5) Add the following logic in standalone program BrowseScene7AssetsByFolder to connect to Scene7, get and parse response....

        import com.scene7.ipsapi.AuthHeader;
        import com.scene7.ipsapi.GetFolderTreeParam;
        import com.scene7.ipsapi.SearchAssetsParam;
        import com.scene7.ipsapi.StringArray;
        import org.apache.commons.httpclient.HttpClient;
        import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
        import org.apache.commons.httpclient.methods.PostMethod;
        import org.apache.commons.io.FileUtils;
        import org.w3c.dom.Document;
        import org.w3c.dom.Element;
        import org.w3c.dom.Node;
        import org.w3c.dom.NodeList;

        import javax.xml.bind.JAXBContext;
        import javax.xml.bind.JAXBException;
        import javax.xml.bind.Marshaller;
        import javax.xml.parsers.DocumentBuilder;
        import javax.xml.parsers.DocumentBuilderFactory;
        import javax.xml.xpath.XPath;
        import javax.xml.xpath.XPathConstants;
        import javax.xml.xpath.XPathFactory;
        import java.io.ByteArrayInputStream;
        import java.io.StringWriter;
        import java.util.ArrayList;
        import java.util.List;

        public class BrowseScene7AssetsByFolder {
        private static String S7_NA_IPS_URL = "https://s7sps1apissl.scene7.com/scene7/api/IpsApiService"; //available in AEM http://localhost:4502/libs/settings/dam/scene7/endpoints.html
        private static String S7_COMPANY_HANDLE = "c|230095"; //c|230095, c|9686 available via api or in AEM /conf/global/settings/cloudconfigs/dmscene7/jcr:content
        private static String S7_USER = "";
        private static String S7_PASS = "";
        private static String STAND_ALONE_APP_NAME = "Experiencing AEM";
        private static int assetCount = 0;

        public static void main(String[] args) throws Exception {
        browseFolder(null);
        System.out.println("TOTAL ASSETS : " + assetCount);
        }

        private static void browseFolder(S7Folder s7Folder) throws Exception {
        AuthHeader authHeader = getS7AuthHeader();

        Marshaller marshaller = getMarshaller(AuthHeader.class);
        StringWriter sw = new StringWriter();
        marshaller.marshal(authHeader, sw);

        String authHeaderStr = sw.toString();

        GetFolderTreeParam getFolderTreeParam = getGetFolderTreeParam(s7Folder);

        marshaller = getMarshaller(getFolderTreeParam.getClass());
        sw = new StringWriter();
        marshaller.marshal(getFolderTreeParam, sw);

        String apiMethod = sw.toString();

        byte[] responseBody = getResponse(authHeaderStr, apiMethod);

        List<S7Folder> folders = parseResponse(responseBody, s7Folder);

        folders.forEach(folder -> {
        if(folder.isHasSubFolders()){
        try{
        browseFolder(folder);
        }catch (Exception e){
        e.printStackTrace();
        }
        }
        });

        printFolders(s7Folder, folders);
        }

        private static void printFolders(S7Folder parentFolder, List<S7Folder> folders){
        if( ( parentFolder == null ) && folders.isEmpty()){
        System.out.println("No folders in account - " + S7_COMPANY_HANDLE);
        return;
        }

        if(folders.isEmpty()){
        System.out.println("No folders under - " + parentFolder.getFolderPath());
        return;
        }

        if(parentFolder == null){
        System.out.println(S7_COMPANY_HANDLE);
        }else{
        System.out.println(parentFolder.getFolderPath());
        }

        folders.forEach(folder -> {
        List<S7Asset> assets = folder.getAssets();

        assetCount = assetCount + assets.size();

        System.out.println("\t" + folder.getFolderPath() + " - " + assets.size());

        assets.forEach(asset -> {
        System.out.println("\t\t" + asset.getAssetPath() + " (" + FileUtils.byteCountToDisplaySize(asset.getAssetSize()) + ")");
        });
        });
        }

        private static List<S7Folder> parseResponse(byte[] responseBody, S7Folder parentFolder) throws Exception{
        List<S7Folder> folders = new ArrayList<S7Folder>();

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        DocumentBuilder builder = factory.newDocumentBuilder();

        ByteArrayInputStream input = new ByteArrayInputStream(responseBody);

        Document doc = builder.parse(input);

        XPath xPath = XPathFactory.newInstance().newXPath();

        String expression = "/getFolderTreeReturn/folders";

        if(parentFolder != null){
        expression = "/getFolderTreeReturn/folders/subfolderArray";
        }

        NodeList itemList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);

        for (int i = 0; i < itemList.getLength(); i++) {
        Node item = itemList.item(i);

        if(parentFolder != null){
        NodeList childList = item.getChildNodes();

        for (int j = 0; j < childList.getLength(); j++) {
        folders.add(fillFolderDetail(childList.item(j)));
        }
        }else{
        folders.add(fillFolderDetail(item));
        }
        }

        return folders;
        }

        private static S7Folder fillFolderDetail(Node item) throws Exception{
        S7Folder s7Folder = new S7Folder();

        if(item.getNodeType() == Node.ELEMENT_NODE) {
        Element eElement = (Element) item;

        s7Folder.setFolderHandle(eElement.getElementsByTagName("folderHandle").item(0).getTextContent());
        s7Folder.setFolderPath(eElement.getElementsByTagName("path").item(0).getTextContent());
        s7Folder.setHasSubFolders(new Boolean(eElement.getElementsByTagName("hasSubfolders").item(0).getTextContent()));

        fillAssetDetails(s7Folder);
        }

        return s7Folder;
        }

        private static void fillAssetDetails(S7Folder s7Folder) throws Exception{
        AuthHeader authHeader = getS7AuthHeader();

        Marshaller marshaller = getMarshaller(AuthHeader.class);
        StringWriter sw = new StringWriter();
        marshaller.marshal(authHeader, sw);

        String authHeaderStr = sw.toString();

        SearchAssetsParam searchAssetsParam = getSearchAssetsParam(s7Folder);

        marshaller = getMarshaller(searchAssetsParam .getClass());
        sw = new StringWriter();
        marshaller.marshal(searchAssetsParam , sw);

        String apiMethod = sw.toString();

        byte[] responseBody = getResponse(authHeaderStr, apiMethod);

        parseSearchResponse(responseBody, s7Folder);
        }

        private static void parseSearchResponse(byte[] responseBody, S7Folder parentFolder) throws Exception{
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        DocumentBuilder builder = factory.newDocumentBuilder();

        ByteArrayInputStream input = new ByteArrayInputStream(responseBody);

        Document doc = builder.parse(input);

        XPath xPath = XPathFactory.newInstance().newXPath();

        String expression = "/searchAssetsReturn/assetArray/items";

        NodeList itemList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);

        List<S7Asset> assets = new ArrayList<S7Asset>();

        for (int i = 0; i < itemList.getLength(); i++) {
        Node item = itemList.item(i);

        if(item.getNodeType() != Node.ELEMENT_NODE) {
        continue;
        }

        S7Asset s7asset = new S7Asset();

        Element eElement = (Element) item;

        s7asset.setAssetHandle(getTextContent(eElement, "assetHandle"));
        s7asset.setAssetPath(getTextContent(eElement, "folder") + getTextContent(eElement, "fileName"));

        NodeList imageInfoList = eElement.getElementsByTagName("imageInfo");

        for (int j = 0; j < imageInfoList.getLength(); j++) {
        Node imageInfo = imageInfoList.item(j);

        if(imageInfo.getNodeType() != Node.ELEMENT_NODE) {
        continue;
        }

        s7asset.setAssetSize(new Long(getTextContent((Element)imageInfo, "fileSize")));
        }

        assets.add(s7asset);
        }

        parentFolder.setAssets(assets);
        }

        private static String getTextContent(Element eElement, String tagName){
        return eElement.getElementsByTagName(tagName).item(0).getTextContent();
        }

        private static byte[] getResponse(String authHeaderStr, String apiMethod) throws Exception{
        StringBuilder requestBody = new StringBuilder("<Request xmlns=\"http://www.scene7.com/IpsApi/xsd/2017-10-29-beta\">");
        requestBody.append(authHeaderStr).append(apiMethod).append("</Request>");

        byte[] responseBody = null;
        PostMethod postMethod = null;

        try {
        HttpClient httpclient = new HttpClient();

        postMethod = new PostMethod(S7_NA_IPS_URL);

        postMethod.setRequestHeader("Content-Type", "text/xml");
        postMethod.setRequestEntity(new ByteArrayRequestEntity(requestBody.toString().getBytes()));

        int responseCode = httpclient.executeMethod(postMethod);

        if(responseCode != 200){
        System.out.println("Response code - " + responseCode + ", returning here...");
        }

        responseBody = postMethod.getResponseBody();
        }finally{
        if(postMethod != null){
        postMethod.releaseConnection();
        }
        }

        return responseBody;
        }

        private static AuthHeader getS7AuthHeader(){
        AuthHeader authHeader = new AuthHeader();

        authHeader.setUser(S7_USER);
        authHeader.setPassword(S7_PASS);
        authHeader.setAppName(STAND_ALONE_APP_NAME);
        authHeader.setAppVersion("1.0");
        authHeader.setFaultHttpStatusCode(new Integer(200));

        return authHeader;
        }

        private static GetFolderTreeParam getGetFolderTreeParam(S7Folder s7Folder){
        GetFolderTreeParam getFolderTreeParam = new GetFolderTreeParam();

        getFolderTreeParam.setCompanyHandle(S7_COMPANY_HANDLE);

        getFolderTreeParam.setDepth(1);

        if(s7Folder != null){
        getFolderTreeParam.setFolderPath(s7Folder.getFolderPath());
        }

        return getFolderTreeParam;
        }

        private static SearchAssetsParam getSearchAssetsParam(S7Folder s7Folder){
        SearchAssetsParam searchAssetsParam = new SearchAssetsParam();
        StringArray assetTypes = new StringArray();
        assetTypes.getItems().add("Image");
        assetTypes.getItems().add("Video");

        searchAssetsParam.setCompanyHandle(S7_COMPANY_HANDLE);
        searchAssetsParam.setFolder(s7Folder.getFolderPath());
        searchAssetsParam.setIncludeSubfolders(false);
        searchAssetsParam.setAssetTypeArray(assetTypes);

        return searchAssetsParam;
        }

        private static Marshaller getMarshaller(Class apiMethodClass) throws JAXBException {
        Marshaller marshaller = JAXBContext.newInstance(new Class[]{apiMethodClass}).createMarshaller();
        marshaller.setProperty("jaxb.formatted.output", Boolean.valueOf(true));
        marshaller.setProperty("jaxb.fragment", Boolean.valueOf(true));
        return marshaller;
        }

        private static class S7Folder{
        private String folderHandle;
        private String folderPath;
        private boolean hasSubFolders;
        private List<S7Asset> assets;

        public String getFolderHandle() {
        return folderHandle;
        }

        public void setFolderHandle(String folderHandle) {
        this.folderHandle = folderHandle;
        }

        public String getFolderPath() {
        return folderPath;
        }

        public void setFolderPath(String folderPath) {
        this.folderPath = folderPath;
        }

        public boolean isHasSubFolders() {
        return hasSubFolders;
        }

        public void setHasSubFolders(boolean hasSubFolders) {
        this.hasSubFolders = hasSubFolders;
        }

        public List<S7Asset> getAssets() {
        return assets;
        }

        public void setAssets(List<S7Asset> assets) {
        this.assets = assets;
        }

        public String toString(){
        return "[" + folderHandle + "," + folderPath + "," + hasSubFolders + "]";
        }
        }

        private static class S7Asset{
        private String assetHandle;
        private String assetPath;
        private long assetSize;

        public String getAssetHandle() {
        return assetHandle;
        }

        public void setAssetHandle(String assetHandle) {
        this.assetHandle = assetHandle;
        }

        public String getAssetPath() {
        return assetPath;
        }

        public void setAssetPath(String assetPath) {
        this.assetPath = assetPath;
        }

        public long getAssetSize() {
        return assetSize;
        }

        public void setAssetSize(long assetSize) {
        this.assetSize = assetSize;
        }
        }
        }


        6) Sample output running the program from a local JVM...





        AEM 6550 - Assets Dynamic Media Standalone Script to Export Assets from Classic (Scene7) Account

        $
        0
        0

        Goal


        You have a AEM Dynamic Media instance and the requirement is to export assets by folder in associated Scene7 account. The following standalone java program explores using the Scene7 wsdl IpsAPI to connect, browse, list and export assets by folder....

        Please note:
        • The following script is to just give you an idea on how to use the API; based on number and size of assets in the S7 account, you might see out of memory exceptions, execution taking too long etc... so improve the code to handle memory utilization, add logic for parallel connections to scene7 etc (when you do and make it open source please leave a comment)
        • You can export upto 500 assets / 1GB per job. So additional logic required if a folder (not including subfolders) have more than 500 assets or 1GB combined...  https://docs.adobe.com/content/help/en/dynamic-media-classic/using/managing-assets/exporting-assets-scene7-publishing-system.html
        • The long zip names (scene7 folder path used in name) for downloads might cause issues in some OS. Adjust the logic if you have deep nested hierarchy...
        For searching check this post

        Github

        Solution


        1) Login to AEM CrxDe - http://localhost:4502/crx/de and get the Scene7 Company handle used in Dynamic Media configuration eg. /conf/global/settings/cloudconfigs/dmscene7/jcr:content



        2) Open following AEM config page and from the list of scene7 endpoints get the one for your region (available in AEM DM configuration, step 1 above eg. northamerica-enterprise)

                             http://localhost:4502/libs/settings/dam/scene7/endpoints.html



        3) Get the Scene7 IpsAPI wsdl SDK from AEM installation launchpad, by looking for the bundle number folder, copy to your program classpath folder and rename bundle.jar to versioned jar name eg. cq-scene7-wsdl-1.3.2.jar



        4) In your standalone program eg. ExportScene7Assets classpath add the following jars (available in Adobe Repo - https://repo.adobe.com/nexus/content/groups/public/)

                                    C:\dev\projects\templibs\scene7\cq-scene7-wsdl-1.3.2.jar
                                    C:\Users\<user>\.m2\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar
                                    C:\Users\<user>\.m2\repository\commons-httpclient\commons-httpclient\3.1\commons-httpclient-3.1.jar
                                    C:\Users\<user>\.m2\repository\commons-logging\commons-logging-api\1.1\commons-logging-api-1.1.jar 
                                    C:\Users\<user>\.m2\repository\commons-io\commons-io\2.5\commons-io-2.5.jar
                                    C:\Users\nalabotu\.m2\repository\org\apache\httpcomponents\httpclient-osgi\4.5.2\httpclient-osgi-4.5.2.jar
                                    C:\Users\nalabotu\.m2\repository\org\apache\httpcomponents\httpcore\4.4.11\httpcore-4.4.11.jar

          5) Sample Request / Response for exporting assets in a folder

                         List assets in a folder

                                        Request

                                        Response



                         Submit Export Job

                                        Request

                                        Response

                         Export Job Status

                                        Request

                                        Response


                         Zips Saved on Disk



                         Export Log




          6)) Add the following logic in standalone program ExportScene7Assets to connect to Scene7, get and parse response, download folder zips....

          import com.scene7.ipsapi.*;
          import org.apache.commons.httpclient.HttpClient;
          import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
          import org.apache.commons.httpclient.methods.PostMethod;
          import org.apache.commons.io.FileUtils;
          import org.apache.http.client.fluent.Request;
          import org.w3c.dom.Document;
          import org.w3c.dom.Element;
          import org.w3c.dom.Node;
          import org.w3c.dom.NodeList;

          import javax.xml.bind.JAXBContext;
          import javax.xml.bind.JAXBException;
          import javax.xml.bind.Marshaller;
          import javax.xml.parsers.DocumentBuilder;
          import javax.xml.parsers.DocumentBuilderFactory;
          import javax.xml.xpath.XPath;
          import javax.xml.xpath.XPathConstants;
          import javax.xml.xpath.XPathFactory;
          import java.io.ByteArrayInputStream;
          import java.io.File;
          import java.io.StringWriter;
          import java.nio.file.Files;
          import java.nio.file.Paths;
          import java.util.ArrayList;
          import java.util.Date;
          import java.util.List;

          public class ExportScene7Assets {
          private static String S7_NA_IPS_URL = "https://s7sps1apissl.scene7.com/scene7/api/IpsApiService"; //available in AEM http://localhost:4502/libs/settings/dam/scene7/endpoints.html
          private static String S7_COMPANY_HANDLE = "c|230095"; //c|230095, c|9686 available via api or in AEM /conf/global/settings/cloudconfigs/dmscene7/jcr:content
          private static String S7_USER = "";
          private static String S7_PASS = "";
          private static String STAND_ALONE_APP_NAME = "Experiencing AEM";
          private static int assetCount = 0;
          private static String DOWNLOAD_ZIPS_LOCATION = "C:/dev/projects/eaem-extensions/eaem-65-extensions/eaem-scene7-search-standalone/out";

          public static void main(String[] args) throws Exception {
          new File(DOWNLOAD_ZIPS_LOCATION).mkdirs();

          browseFolder(null);

          System.out.println("TOTAL ASSETS : " + assetCount);
          }

          private static void browseFolder(S7Folder s7Folder) throws Exception {
          AuthHeader authHeader = getS7AuthHeader();

          Marshaller marshaller = getMarshaller(AuthHeader.class);
          StringWriter sw = new StringWriter();
          marshaller.marshal(authHeader, sw);

          String authHeaderStr = sw.toString();

          GetFolderTreeParam getFolderTreeParam = getGetFolderTreeParam(s7Folder);

          marshaller = getMarshaller(getFolderTreeParam.getClass());
          sw = new StringWriter();
          marshaller.marshal(getFolderTreeParam, sw);

          String apiMethod = sw.toString();

          byte[] responseBody = getResponse(authHeaderStr, apiMethod);

          List<S7Folder> folders = parseResponse(responseBody, s7Folder);

          folders.forEach(folder -> {
          if(folder.isHasSubFolders()){
          try{
          browseFolder(folder);
          }catch (Exception e){
          e.printStackTrace();
          }
          }
          });

          printFolders(s7Folder, folders);
          }

          private static void printFolders(S7Folder parentFolder, List<S7Folder> folders){
          if( ( parentFolder == null ) && folders.isEmpty()){
          System.out.println("No folders in account - " + S7_COMPANY_HANDLE);
          return;
          }

          if(folders.isEmpty()){
          System.out.println("No folders under - " + parentFolder.getFolderPath());
          return;
          }

          if(parentFolder == null){
          System.out.println(S7_COMPANY_HANDLE);
          }else{
          System.out.println(parentFolder.getFolderPath());
          }

          folders.forEach(folder -> {
          List<S7Asset> assets = folder.getAssets();

          assetCount = assetCount + assets.size();

          System.out.println("\t" + folder.getFolderPath() + " - " + assets.size());

          assets.forEach(asset -> {
          System.out.println("\t\t" + asset.getAssetPath() + " (" + FileUtils.byteCountToDisplaySize(asset.getAssetSize()) + ")");
          });

          if(!folder.getAssets().isEmpty()){
          try{
          exportAssets(folder);
          }catch (Exception e){
          System.out.println("ERROR EXPORTING ASSETS OF - " + folder.getFolderPath());
          }
          }
          });
          }

          private static void exportAssets(S7Folder folder) throws Exception{
          AuthHeader authHeader = getS7AuthHeader();

          Marshaller marshaller = getMarshaller(AuthHeader.class);
          StringWriter sw = new StringWriter();
          marshaller.marshal(authHeader, sw);

          String authHeaderStr = sw.toString();

          SubmitJobParam submitJobParam = getSubmitJobParam(folder);

          marshaller = getMarshaller(submitJobParam.getClass());
          sw = new StringWriter();
          marshaller.marshal(submitJobParam, sw);

          String apiMethod = sw.toString();

          byte[] responseBody = getResponse(authHeaderStr, apiMethod);

          String exportJobHandle = parseSubmitJobResponse(responseBody, folder);

          if(exportJobHandle == null){
          return;
          }

          String exportZipUrl = getExportZipUrl(authHeaderStr, submitJobParam.getJobName());

          System.out.println("\t\tEXPORT ZIP URL - " + exportZipUrl);

          byte[] response = Request.Get(exportZipUrl).execute().returnContent().asBytes();

          if (response == null || response.length == 0) {
          throw new RuntimeException("Empty response for download request");
          }

          File zipFile = new File(DOWNLOAD_ZIPS_LOCATION + "/" + submitJobParam.getJobName() + ".zip");

          Files.write(Paths.get(zipFile.getAbsolutePath()), response);

          System.out.println("\t\tSAVED AS - " + zipFile.getAbsolutePath());
          }

          private static String getExportZipUrl(String authHeaderStr, String jobName) throws Exception{
          String exportZipUrl = null;

          do{
          Thread.sleep(500);

          exportZipUrl = pingJobStatus(authHeaderStr, jobName);
          }while( exportZipUrl == null);

          return exportZipUrl;
          }

          private static String pingJobStatus(String authHeaderStr, String jobName) throws Exception{
          GetJobLogDetailsParam getJobLogDetails = getJobLogDetails(jobName);

          Marshaller marshaller = getMarshaller(getJobLogDetails.getClass());
          StringWriter sw = new StringWriter();
          marshaller.marshal(getJobLogDetails, sw);

          String apiMethod = sw.toString();

          byte[] responseBody = getResponse(authHeaderStr, apiMethod);

          return parseJobLogResponse(responseBody);
          }

          private static String parseJobLogResponse(byte[] responseBody) throws Exception{
          String expression = "//assetName[starts-with(text(),'http')]";

          NodeList itemList = getDocumentNodeList(responseBody, expression);

          String exportZipUrl = null;

          for (int i = 0; i < itemList.getLength(); i++) {
          Node item = itemList.item(i);

          if(item.getNodeType() != Node.ELEMENT_NODE) {
          continue;
          }

          exportZipUrl = ((Element)item).getTextContent();

          break;
          }

          return exportZipUrl;
          }


          private static String parseSubmitJobResponse(byte[] responseBody, S7Folder parentFolder) throws Exception{
          String expression = "/submitJobReturn/jobHandle";

          NodeList itemList = getDocumentNodeList(responseBody, expression);

          String exportJobHandle = null;

          for (int i = 0; i < itemList.getLength(); i++) {
          Node item = itemList.item(i);

          if(item.getNodeType() != Node.ELEMENT_NODE) {
          continue;
          }

          exportJobHandle = ((Element)item).getTextContent();

          System.out.println("\t\tEXPORT JOB - " + exportJobHandle);

          break;
          }

          return exportJobHandle;
          }

          private static List<S7Folder> parseResponse(byte[] responseBody, S7Folder parentFolder) throws Exception{
          List<S7Folder> folders = new ArrayList<S7Folder>();

          String expression = "/getFolderTreeReturn/folders";

          if(parentFolder != null){
          expression = "/getFolderTreeReturn/folders/subfolderArray";
          }

          NodeList itemList = getDocumentNodeList(responseBody, expression);

          for (int i = 0; i < itemList.getLength(); i++) {
          Node item = itemList.item(i);

          if(parentFolder != null){
          NodeList childList = item.getChildNodes();

          for (int j = 0; j < childList.getLength(); j++) {
          folders.add(fillFolderDetail(childList.item(j)));
          }
          }else{
          folders.add(fillFolderDetail(item));
          }
          }

          return folders;
          }

          private static S7Folder fillFolderDetail(Node item) throws Exception{
          S7Folder s7Folder = new S7Folder();

          if(item.getNodeType() == Node.ELEMENT_NODE) {
          Element eElement = (Element) item;

          s7Folder.setFolderHandle(getTextContent(eElement, "folderHandle"));
          s7Folder.setFolderPath(getTextContent(eElement, "path"));
          s7Folder.setHasSubFolders(new Boolean(getTextContent(eElement, "hasSubfolders")));

          fillAssetDetails(s7Folder);
          }

          return s7Folder;
          }

          private static void fillAssetDetails(S7Folder s7Folder) throws Exception{
          AuthHeader authHeader = getS7AuthHeader();

          Marshaller marshaller = getMarshaller(AuthHeader.class);
          StringWriter sw = new StringWriter();
          marshaller.marshal(authHeader, sw);

          String authHeaderStr = sw.toString();

          SearchAssetsParam searchAssetsParam = getSearchAssetsParam(s7Folder);

          marshaller = getMarshaller(searchAssetsParam .getClass());
          sw = new StringWriter();
          marshaller.marshal(searchAssetsParam , sw);

          String apiMethod = sw.toString();

          byte[] responseBody = getResponse(authHeaderStr, apiMethod);

          parseSearchResponse(responseBody, s7Folder);
          }

          private static void parseSearchResponse(byte[] responseBody, S7Folder parentFolder) throws Exception{
          String expression = "/searchAssetsReturn/assetArray/items";

          NodeList itemList = getDocumentNodeList(responseBody, expression);

          List<S7Asset> assets = new ArrayList<S7Asset>();

          for (int i = 0; i < itemList.getLength(); i++) {
          Node item = itemList.item(i);

          if(item.getNodeType() != Node.ELEMENT_NODE) {
          continue;
          }

          S7Asset s7asset = new S7Asset();

          Element eElement = (Element) item;

          s7asset.setAssetHandle(getTextContent(eElement, "assetHandle"));
          s7asset.setAssetPath(getTextContent(eElement, "folder") + getTextContent(eElement, "fileName"));

          NodeList imageInfoList = eElement.getElementsByTagName("imageInfo");

          for (int j = 0; j < imageInfoList.getLength(); j++) {
          Node imageInfo = imageInfoList.item(j);

          if(imageInfo.getNodeType() != Node.ELEMENT_NODE) {
          continue;
          }

          s7asset.setAssetSize(new Long(getTextContent((Element)imageInfo, "fileSize")));
          }

          assets.add(s7asset);
          }

          parentFolder.setAssets(assets);
          }

          private static String getTextContent(Element eElement, String tagName){
          return eElement.getElementsByTagName(tagName).item(0).getTextContent();
          }

          private static byte[] getResponse(String authHeaderStr, String apiMethod) throws Exception{
          StringBuilder requestBody = new StringBuilder("<Request xmlns=\"http://www.scene7.com/IpsApi/xsd/2017-10-29-beta\">");
          requestBody.append(authHeaderStr).append(apiMethod).append("</Request>");

          byte[] responseBody = null;
          PostMethod postMethod = null;

          try {
          HttpClient httpclient = new HttpClient();

          postMethod = new PostMethod(S7_NA_IPS_URL);

          postMethod.setRequestHeader("Content-Type", "text/xml");

          System.out.println("REQUEST - " + requestBody);

          postMethod.setRequestEntity(new ByteArrayRequestEntity(requestBody.toString().getBytes()));

          int responseCode = httpclient.executeMethod(postMethod);

          if(responseCode != 200){
          System.out.println("Response code - " + responseCode + ", returning here...");
          }

          responseBody = postMethod.getResponseBody();

          System.out.println("RESPONSE - " + new String(responseBody));
          }finally{
          if(postMethod != null){
          postMethod.releaseConnection();
          }
          }

          return responseBody;
          }

          private static AuthHeader getS7AuthHeader(){
          AuthHeader authHeader = new AuthHeader();

          authHeader.setUser(S7_USER);
          authHeader.setPassword(S7_PASS);
          authHeader.setAppName(STAND_ALONE_APP_NAME);
          authHeader.setAppVersion("1.0");
          authHeader.setFaultHttpStatusCode(new Integer(200));

          return authHeader;
          }

          private static GetFolderTreeParam getGetFolderTreeParam(S7Folder s7Folder){
          GetFolderTreeParam getFolderTreeParam = new GetFolderTreeParam();

          getFolderTreeParam.setCompanyHandle(S7_COMPANY_HANDLE);

          getFolderTreeParam.setDepth(1);

          if(s7Folder != null){
          getFolderTreeParam.setFolderPath(s7Folder.getFolderPath());
          }

          return getFolderTreeParam;
          }

          private static SearchAssetsParam getSearchAssetsParam(S7Folder s7Folder){
          SearchAssetsParam searchAssetsParam = new SearchAssetsParam();
          StringArray assetTypes = new StringArray();
          assetTypes.getItems().add("Image");
          assetTypes.getItems().add("Video");

          searchAssetsParam.setCompanyHandle(S7_COMPANY_HANDLE);
          searchAssetsParam.setFolder(s7Folder.getFolderPath());
          searchAssetsParam.setIncludeSubfolders(false);
          searchAssetsParam.setAssetTypeArray(assetTypes);

          return searchAssetsParam;
          }

          private static SubmitJobParam getSubmitJobParam(S7Folder s7Folder){
          SubmitJobParam submitJobParam = new SubmitJobParam();

          ExportJob exportJob = new ExportJob();
          exportJob.setFmt("orig");
          exportJob.setEmailSetting("All");

          HandleArray assetHandles = new HandleArray();

          s7Folder.getAssets().forEach( asset ->{
          assetHandles.getItems().add(asset.getAssetHandle());
          });

          exportJob.setAssetHandleArray(assetHandles);

          submitJobParam.setCompanyHandle(S7_COMPANY_HANDLE);
          submitJobParam.setJobName(getExportJobName(s7Folder.getFolderPath()));
          submitJobParam.setExportJob(exportJob);

          return submitJobParam;
          }

          private static GetJobLogDetailsParam getJobLogDetails(String origJobName){
          GetJobLogDetailsParam getJobLogDetailsParam = new GetJobLogDetailsParam();

          getJobLogDetailsParam.setCompanyHandle(S7_COMPANY_HANDLE);
          getJobLogDetailsParam.setOriginalName(origJobName);

          return getJobLogDetailsParam;
          }

          private static GetActiveJobsParam getActiveJobsParam(String jobHandle){
          GetActiveJobsParam getActiveJobsParam = new GetActiveJobsParam();

          getActiveJobsParam.setCompanyHandle(S7_COMPANY_HANDLE);
          getActiveJobsParam.setJobHandle(jobHandle);

          return getActiveJobsParam;
          }

          private static NodeList getDocumentNodeList(byte[] responseBody, String expression) throws Exception{
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

          DocumentBuilder builder = factory.newDocumentBuilder();

          ByteArrayInputStream input = new ByteArrayInputStream(responseBody);

          Document doc = builder.parse(input);

          XPath xPath = XPathFactory.newInstance().newXPath();

          return (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);
          }

          private static String getExportJobName(String folderPath){
          return folderPath.replaceAll("/", "_") + (new Date()).getTime();
          }

          private static Marshaller getMarshaller(Class apiMethodClass) throws JAXBException {
          Marshaller marshaller = JAXBContext.newInstance(new Class[]{apiMethodClass}).createMarshaller();
          marshaller.setProperty("jaxb.formatted.output", Boolean.valueOf(true));
          marshaller.setProperty("jaxb.fragment", Boolean.valueOf(true));
          return marshaller;
          }

          private static class S7Folder{
          private String folderHandle;
          private String folderPath;
          private boolean hasSubFolders;
          private List<S7Asset> assets;

          public String getFolderHandle() {
          return folderHandle;
          }

          public void setFolderHandle(String folderHandle) {
          this.folderHandle = folderHandle;
          }

          public String getFolderPath() {
          return folderPath;
          }

          public void setFolderPath(String folderPath) {
          this.folderPath = folderPath;
          }

          public boolean isHasSubFolders() {
          return hasSubFolders;
          }

          public void setHasSubFolders(boolean hasSubFolders) {
          this.hasSubFolders = hasSubFolders;
          }

          public List<S7Asset> getAssets() {
          if(assets == null){
          return new ArrayList<>();
          }

          return assets;
          }

          public void setAssets(List<S7Asset> assets) {
          this.assets = assets;
          }

          public String toString(){
          return "[" + folderHandle + "," + folderPath + "," + hasSubFolders + "]";
          }
          }

          private static class S7Asset{
          private String assetHandle;
          private String assetPath;
          private long assetSize;

          public String getAssetHandle() {
          return assetHandle;
          }

          public void setAssetHandle(String assetHandle) {
          this.assetHandle = assetHandle;
          }

          public String getAssetPath() {
          return assetPath;
          }

          public void setAssetPath(String assetPath) {
          this.assetPath = assetPath;
          }

          public long getAssetSize() {
          return assetSize;
          }

          public void setAssetSize(long assetSize) {
          this.assetSize = assetSize;
          }
          }
          }









          AEM 6550 - Create a sample SPA React App using AEM SPA Editor

          $
          0
          0

          This tutorial on official adobe docs is an excellent guide for creating your first AEM SPA React App, however if you are looking for some quick steps, go through the following... 




          1) Create the project structure (for both React SPA and traditional authoring) with the following command using maven archetype - https://github.com/adobe/aem-project-archetype

          mvn -B archetype:generate -D archetypeGroupId=com.adobe.granite.archetypes -D archetypeArtifactId=aem-project-archetype 
          -D archetypeVersion=23 -D aemVersion=6.5.0 -D appTitle="Experience AEM SPA React" -D appId="eaem-sites-spa-how-to-react" -D groupId="com.eaem"
          -D frontendModule=react -D includeExamples=n -D includeErrorHandler=n -D includeDispatcherConfig=n

          2) The argument -D frontendModule=react in above command creates a ui.frontend folder with a react app (and sample components) adding the following AEM SPA editor npm dependencies in ui.frontend/package.json

          "scripts": {
          ...
          "build": "react-scripts build && clientlib",
          ...
          "sync": "aemsync -d -w ../ui.apps/src/main/content"
          },

          "dependencies": {
          "@adobe/cq-react-editable-components": "^1.2.0",
          "@adobe/cq-spa-component-mapping": "^1.0.3",
          "@adobe/cq-spa-page-model-manager": "^1.1.0",
          ...
          },
          "devDependencies": {
          ...
          "aem-clientlib-generator": "^1.5.0",
          "aemsync": "^4.0.0",
          ...
          }

          3) If you are not using maven archetype or a prebuilt package.json, but creating the structure on your own, you'd probably be executing the following commands....

                                 a. Install latest version of Node and NPM from https://nodejs.org/en/ 
                                 b. Install create-react-app  npm install --g create-react-app
                                 c. Create the react app create-react-app eaem-sites-spa-how-to-react
                                 d. Quick build check npm run build
                                 e. Install the AEM client lib generator npm install aem-clientlib-generator --save-dev
                                 f. Check the clientlib configuration in eaem-sites-spa-how-to-react\clientlib.config.js
                                 g. Edit package.json in the eaem-sites-spa-how-to-react to update the build command. "build": "react-scripts build && clientlib"
                                 g. Install AEM JS SDK for SPA editor 
                                                            npm install @adobe/cq-spa-component-mapping
                                                            npm install @adobe/cq-spa-page-model-manager
                                                            npm install @adobe/cq-react-editable-components
                                 i. Install react router dependencies
                                                            npm install --save react-router 
                                                            npm install --save react-router-dom
                                 i. Install aem sync npm install -g aemsync
                                                            
          4) After creating the project structure, removed all unnecessary modules and sample code generated to keep only the following components required to build our simple SPA site...

                                 /apps/eaem-sites-spa-how-to-react/components/image
                                 /apps/eaem-sites-spa-how-to-react/components/navigation
                                 /apps/eaem-sites-spa-how-to-react/components/text
                                 /apps/eaem-sites-spa-how-to-react/components/title                       

          5) The following spa editable templates are also created by archetype for the app root and app rages...

                                 /conf/eaem-sites-spa-how-to-react/settings/wcm/templates/spa-app-template
                                 /conf/eaem-sites-spa-how-to-react/settings/wcm/templates/spa-page-template

          6) Added the following template type in source code for creating new spa editable templates...

                                 /conf/eaem-sites-spa-how-to-react/settings/wcm/template-types/spa-page

          7) Let's create a new component Image for using in the SPA editor (sample package in Github has other components like Navigation, Text...)

          8) Create the component folder ui.apps\src\main\content\jcr_root\apps\eaem-sites-spa-how-to-react\components\image and the cq:dialog with following code
          <?xml version="1.0" encoding="UTF-8"?>
          <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
          jcr:primaryType="nt:unstructured"
          jcr:title="Image"
          sling:resourceType="cq/gui/components/authoring/dialog">
          <content
          jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/container">
          <items jcr:primaryType="nt:unstructured">
          <tabs
          jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/tabs"
          maximized="{Boolean}true">
          <items jcr:primaryType="nt:unstructured">
          <image
          jcr:primaryType="nt:unstructured"
          jcr:title="Image"
          sling:resourceType="granite/ui/components/coral/foundation/container"
          margin="{Boolean}true">
          <items jcr:primaryType="nt:unstructured">
          <columns
          jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
          margin="{Boolean}true">
          <items jcr:primaryType="nt:unstructured">
          <column
          jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/container">
          <items jcr:primaryType="nt:unstructured">
          <image
          jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
          fieldLabel="Image"
          name="./imageURL"
          renderReadOnly="{Boolean}false"
          required="{Boolean}true"
          rootPath="/content/dam"/>
          <imagelink
          jcr:primaryType="nt:unstructured"
          sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
          fieldDescription="Enter the Link URL"
          fieldLabel="Image Link"
          name="./imageLink"
          rootPath="/content/eaem-sites-spa-how-to-react/us/en"/>
          </items>
          </column>
          </items>
          </columns>
          </items>
          </image>
          </items>
          </tabs>
          </items>
          </content>
          </jcr:root>

          9) Add the following code in ui.frontend\src\components\Image\Image.js. React uses ES6 based language and transpiles the code to javascript. No HTL code is required in AEM, because the display will be handled by script in React. 

          import { MapTo } from '@adobe/cq-react-editable-components';
          import DOMPurify from 'dompurify';
          import React, { Component } from 'react';
          import {Link} from "react-router-dom";

          const ImageEditConfig = {
          emptyLabel: 'Image - Experience AEM',

          isEmpty: function (props) {
          return (!props || !props.imageURL || (props.imageURL.trim().length < 1));
          }
          };

          class Image extends Component {
          get imageHTML() {
          const imgStyles = {
          "display": 'block',
          "margin-left": 'auto',
          "margin-right": 'auto'
          };

          return (
          <div>
          <Link to={this.props.imageLink}>
          <img src={this.props.imageURL} style={imgStyles}/>
          </Link>
          </div>
          );
          }

          render() {
          return this.imageHTML;
          }
          }

          export default MapTo('eaem-sites-spa-how-to-react/components/image')(Image,ImageEditConfig);

          10) #36 in above code states this react component is mapped to AEM component eaem-sites-spa-how-to-react/components/image

          11) The page model in json format for above component is provided by Sling Model Jackson Exporter (selector .model.json) available at url http://localhost:4502/content/eaem-sites-spa-how-to-react/us/en.model.json



          12) For testing any quick edits to the render script in react app, run aem sync to listen to any code changes in the app and sync them to AEM...



          13) After making code edits in render script, run npm run build to build the react app JS which is copied to \ui.apps\src\main\content\jcr_root\apps\eaem-sites-spa-how-to-react\clientlibs\clientlib-react by aem sync (configuration provided in ui.frontend\clientlib.config.js)



          14) Also removed the npm run test from frontend-maven-plugin in ui.frontend\pom.xml ( if there are no unit tests for react components )
          Viewing all 525 articles
          Browse latest View live


          <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>