Posts tagged "cq"

July 22, 2014

AEM6 + Mongo Minimal Setup

With the release of AEM 6.0, clustering has been rethought in the form of Apache Jackrabbit “Oak” and the introduction to the concept of microkernels. The initial microkernel that supports clustering uses MongoDB (v. 2.6+) for persistence. This post will provide a real working example of a minimal authoring cluster using a fault-tolerant MongoDB microkernel.

If I receive enough demand, I can also post a production quality MongoDB setup.

What you’ll need:

  • MongoDB installed
  • AEM 6.0 quickstart jar
  • Oracle JRE 1.7

The examples provided were tested using an OS X host but should be compatible on Windows as well as other Unix hosts.

Prep Work

  1. Install the MongoDB binaries
    (I used homebrew: sudo brew install mongodb)
  2. Install the Oracle JDK/JRE v 1.7 (if you haven’t already)
  3. Create a directory for housing your mongo data. I used “aem6_cluster”.
  4. Inside that directory, create these directories: node0,node1,arb0
    The two node directories will be the data directories for two members of a replica set. The arb directory is the data directory for an arbiter.
  5. From the main cluster directory, create another folder named “author”.
  6. Copy your aem 6 quickstart jar and license file to that directory. (Rename the quickstart to this if needed: aem6-author-p4502.jar)

MongoDB Logo

MongoDB setup

  1. From the main cluster directory run this mongo command (from terminal or command prompt):
     mongod --dbpath node0/ --replSet aem6
  2. Enter the mongo util in another terminal window:
     mongo
  3. Initialize the replica set named in step 7:
     rs.initiate(rsconf = {_id: "aem6", members: [{_id: 0, host: "<hostname>:27017"}]})
  4. After the command is issued, it should return a json response with “ok”: 1. Keep this terminal window open.
  5. From the main cluster directory run this mongo command (from a new terminal or command prompt):
     mongod --dbpath node1/ --replSet aem6 --port 27018
  6. Back in the mongo util window that we kept open, issue this command to add the new node to the replica set:
     rs.add("<hostname>:27018")
  7. Once again, if the command executes correctly you should get a json response with “ok” : 1. Keep this window open (again).
  8. From the main cluster directory run this mongo command (from a new terminal or command prompt):
     mongod --port 27019 --dbpath arb0 --replSet aem6
  9. Back in the mongo util window that we kept open, issue this command to add this new node as an arbiter:
     rs.addArb("<hostname>:27019")

At this point we have a clean mongo replica set with 3 members. There should be a PRIMARY and SECONDARY with an aribter node to break up any ties for voting who is the PRIMARY. The two nodes provide minimal redundancy. If one goes down, the other takes over. If both go down, you’re up a creek.

AEM Logo

AEM 6 startup

From the author directory, issue this command:

 java -XX:MaxPermSize=512M -mx4g -jar aem6-author-p4502.jar -r crx3,crx3mongo -Doak.mongo.uri=mongodb://<hostname>:27017,<hostname>:27018

AEM should install and replicate automatically to the mongo nodes. Installation on pretty quick SSD-based mac takes less that 5 minutes (results may vary).

You can now repeat this process on any number of AEM instances as long as they are running on separate ports. Each AEM instance will be active+active in the cluster.

Note the run modes: crx,crx3mongo. The JVM settings can be left out – I just like to dedicate a bit more RAM.

Mongo Notes

To test out the fault tolerance, just ctl+c the mongo instance and watch the AEM driver switch to the elected PRIMARY.

Mongo replica sets must have an odd number of members. An arbiter is not required, but it allows you to have nicely divisible replica sets.

1:00 PM Comments (0) Permalink
November 27, 2013

Static multi-image component with image preview

I recently got a requirement in which I had to build a component in which the user can Drag and Drop 2 main images (e.g.: hero) and 3 sub images ( icons,buttons,logos etc). The main requirement was being able to preview these images while the author is creating content for that page.

I got it working after few trials and I am sharing the same.

Environment: CQ561

1. Create a single image component extending the OOTB (xtype=’html5smartimage’) image component. This should be fairly simple and if you need any information on this leave a comment and I can help.

Screen Shot 2013-11-27 at 4.37.12 PM

 

2. Now create another tab in your component structure by copy/pasting your current working image cq:Panel. For E.g.: in my case I copied /apps/blog/components/content/hero/dialog/items/items/imagetab1 and pasted it under /apps/blog/components/content/hero/dialog/items/items/ to create /apps/blog/components/content/hero/dialog/items/items/imagetab2

Screen Shot 2013-11-27 at 4.42.23 PM

 

3. Next to avoid confusion I also renamed the image cq:Widget at /apps/blog/components/content/hero/dialog/items/items/imagetab2/items/image to /apps/blog/components/content/hero/dialog/items/items/imagetab2/items/image4

Screen Shot 2013-11-27 at 4.44.59 PM

 

4. Now we will have to update the properties under image4 cq:Widget (/apps/blog/components/content/hero/dialog/items/items/imagetab2/items/image4) to make this work.

 

table { }td { padding-top: 1px; padding-right: 1px; padding-left: 1px; color: black; font-size: 12pt; font-weight: 400; font-style: normal; text-decoration: none; font-family: Calibri,sans-serif; vertical-align: bottom; border: medium none; white-space: nowrap; }

Property Name New Value
cropParameter ./image4/imagecrop
fileNameParameter ./image4/fileName
fileReferenceParameter ./image4/fielReference
name ./image4/image4file
rotateParameter ./image4/imageRotate

Screen Shot 2013-11-27 at 5.12.56 PM

 

5. Now when you go back to your content page you should have 2 different tabs and both having the image drag and drop functionality. You can extend this concept for more images as long as you make sure the properties are updated correctly for the image cq:Widget.

Screen Shot 2013-11-27 at 5.19.22 PM                                            Screen Shot 2013-11-27 at 5.19.11 PM

6:28 PM Permalink

Installing AEM from command line without sample content (Geometrixx)

When you install a new instance of Adobe Experience Manager a bunch of content is provided as Sample content. By sample content I am referring to the Geometrixx sites (Geometrixx, Geometrixx Mobile, Geometrixx Outdoors and Geometrixx Outdoors Mobile) that are provided as samples and references. They can be removed by uninstalling packages available in AEM. It is also a recommendation to Uninstall example content and users as part of the Adobe Experience Manager 5.6.1 Security Checklist specially in your Production environment.

The good part is that now we also have a  run mode option for installing or not installing the sample content while installing CQ from command line. These modes allow you to control the use of sample content. The sample content is defined before the quickstart is built and can include packages, configurations, etc:

Installation run modes are provided out-of-the-box:

  • author
  • publish

AND

  • samplecontent
  • nosamplecontent

 

Environment in which I have successfully used it had:

  • Oracle Linux
  • java version “1.6.0_65″
  • 64 bit VM

 

These are two pairs of mutually exclusive run modes; for example, you can:

  • define either author or publish, not both at the same time
  • combine author with either samplecontent or nosamplecontent (but not both)

 

Command line installation:

Note:

  • You will need to configure your -XX:MaxPermSize and Xmx based on 32bit or 64bit VM and how much memory you can use.
  • I have renamed my CQ 561 jar to cq56-author-p4502.jar for author instance and cq56-publish-p4503.jar for publish instance.

Author instance installation command:

java -server -XX:MaxPermSize=256m -Xmx1024M -jar cq56-author-p4502.jar -r author, nosamplecontent

Publish instance installation command:

java -server -XX:MaxPermSize=256m -Xmx1024M -jar cq56-publish-p4503.jar -r publish, nosamplecontent

Once CQ gets installed you verify that none of the Geometrixx related content should have got installed.

5:21 PM Permalink
October 4, 2013

Custom Component: Manual List Widget

Requirement: The users want the ability to manually choose a list of of items from a source list of items.

Example: A list of all the articles in the system is the source list and the users want to select the most important ones to be listed on the home page of a site.

To achieve this, I created a custom ExtJs widget.

  • The users are able to double click on an item on the left (source) list to move the said item to the right list
  • They can manually order the items on the right list via drag and drop.
  • They can double click the item on the right list to remove it from the list
  • They are also able to pass tags to limit the results (your custom servlet that returns the JSON object to be consumed by the widget will need to handle this use case)

Below are the steps to use this widget in your implementation.

1. Paste the widget code from below into a JS file in your CQ implementation.

//Create a new class based on existing CompositeField
CQ.form.GridtoGrid = CQ.Ext.extend(CQ.CustomContentPanel, {
    secondGrid: null,
    firstGrid: null,
    columnModel: null,
    reader: null,
    layout: 'fit',
    constructor : function(config){

        var dataSrcUrl;

        if( config.dataSrcUrl != null ){
            dataSrcUrl = config.dataSrcUrl;
        }

        var jsonReaderConfig;
        if( config.jsonReaderConfig != null ){
            jsonReaderConfig = config.jsonReaderConfig;
        }

        var sortDirection;
        if( config.sortDirection != null ){
            sortDirection = config.sortDirection;
        }

        var sortField;
        if( config.sortField != null ){
            sortField = config.sortField;
        }

        var columnModelConfig;
        if( config.columnModelConfig != null ){
            columnModelConfig = config.columnModelConfig;
        }

        console.log( dataSrcUrl );
        console.log( sortDirection );
        console.log( sortField );
        console.log( CQ.Ext.util.JSON.decode( jsonReaderConfig ) );
        console.log( CQ.Ext.util.JSON.decode( columnModelConfig ) );

        var proxy = new CQ.Ext.data.HttpProxy( {url:  dataSrcUrl} );

        reader = new CQ.Ext.data.JsonReader( {}, CQ.Ext.util.JSON.decode( jsonReaderConfig ) );

        var sourceStore = new CQ.Ext.data.Store(
            {
                proxy: proxy,
                sortInfo: {
                    field: sortField,
                    direction: sortDirection
                },
                reader: reader
            }
        );
        sourceStore.load();

        columnModel = new CQ.Ext.grid.ColumnModel( CQ.Ext.util.JSON.decode( columnModelConfig ) );

        firstGrid = new CQ.Ext.grid.GridPanel({
            listeners: {
                rowdblclick: function (grid, index){

                    //On DoubleClick, get the record from the sourceStore using the index.
                    var moveRecord = firstGrid.getStore().getAt( index );

                    //Add the record to the destinationStore.
                    secondGrid.getStore().add( moveRecord );
                }
            },
            store            : sourceStore,
            name             : 'sourceGrid',
            colModel          : columnModel,
            stripeRows       : true,
            title            : 'Latest Articles',
            margins          : '0 2 0 0',
            x: 0,
            y:100,
            height: 500

        });

        //destination store
        var destinationStore = new CQ.Ext.data.Store({
            reader: reader
        });

        //destination grid
        secondGrid = new CQ.Ext.grid.GridPanel({
            listeners: {
                rowdblclick: function (grid, index){
                    //On DoubleClick, get the record from the sourceStore using the index.
                    var moveRecord = secondGrid.getStore().getAt( index );

                    //Remove the record from the destinationStore.
                    secondGrid.getStore().remove( moveRecord );
                }
            },
            name: 'destinationGrid',
            store: destinationStore,
            colModel: columnModel,
            stripeRows       : true,
            title            : 'Displayed Articles',
            enableDragDrop: true,
            hideHeaders: true,
            ddGroup: 'ddGroup',
            margins          : '0 0 0 3',
            x: 450,
            y: 100,
            height: 500
        });

        //Set the dropZone for the second grid so that users can manually re-order the list.
        secondGrid.on('render', function() {

            secondGrid.dropZone = new CQ.Ext.dd.DropZone(secondGrid.getView().scroller, {
                ddGroup: 'ddGroup',

                //If the mouse is over a grid row, return that node. This is
                //provided as the "target" parameter in all "onNodeXXXX" node event handling functions
                getTargetFromEvent: function(e) {
                    return e.getTarget(secondGrid.getView().rowSelector);
                },

                //On entry into a target node, highlight that node.
                onNodeEnter : function(target, dd, e, data){
                    CQ.Ext.fly(target).addClass('my-row-highlight-class');
                },

                //On exit from a target node, unhighlight that node.
                onNodeOut : function(target, dd, e, data){
                    CQ.Ext.fly(target).removeClass('my-row-highlight-class');
                },

                //While over a target node, return the default drop allowed class which
                //places a "tick" icon into the drag proxy.
                onNodeOver : function(target, dd, e, data){
                    return CQ.Ext.dd.DropZone.prototype.dropAllowed;
                },

                //On NodeDrop event
                onNodeDrop : function(target, dd, e, data){

                    var targetRowIndex = secondGrid.getView().findRowIndex(target);
                    var sourceRowIndex = data.rowIndex;

                    if( targetRowIndex == false ){
                        return false;
                    }

                    if( targetRowIndex == sourceRowIndex ){
                        return false;
                    }

                    var ds = secondGrid.getStore();

                    var sourceRecord = ds.getAt(sourceRowIndex);

                    ds.insert( targetRowIndex, sourceRecord );

                    //Reconfigure the destination list to new store.
                    secondGrid.reconfigure ( ds, columnModel );

                    return true;
                }
            });
        })

        //Tag Inputbox
        var tagInputField = new CQ.tagging.TagInputField({
            listeners:{
                addtag: function( inputField, tag ){

                    var proxyUrl = firstGrid.getStore().proxy.url;
                    var tagId = tag.tagID;

                    var tagsIndex = proxyUrl.indexOf( '?tags=' );

                    if( tagsIndex > -1 ){
                        proxyUrl = proxyUrl + "," + tagId;
                    } else {
                        proxyUrl = proxyUrl + "?tags=" + tagId;
                    }
                    //alert( "Add Tag: " + proxyUrl );
                    var updatedSourceStore = new CQ.Ext.data.Store({
                        url: proxyUrl,
                        reader: reader
                    });

                    updatedSourceStore.load();
                    firstGrid.reconfigure( updatedSourceStore, columnModel );
                },
                removetag: function( inputField, tag ){
                    var proxyUrl = firstGrid.getStore().proxy.url;
                    var tagId = tag.tagID;

                    var splitUrl = new Array();
                    splitUrl = proxyUrl.split( '?tags=' );

                    //alert( "Remove Tag 1 : " + proxyUrl );

                    if( splitUrl[1].indexOf( "," + tagId ) > -1 ){
                        splitUrl[1] = splitUrl[1].replace( "," + tagId, "" );
                    } else {
                        splitUrl[1] = splitUrl[1].replace( tagId , "" );
                    }

                    if( splitUrl[1].indexOf( ',' ) == 0 ){
                        splitUrl[1] = splitUrl[1].substring(1);
                    }

                    if( splitUrl[1] != null && splitUrl[1].length > 0 ){
                        proxyUrl = splitUrl[0] + '?tags=' + splitUrl[1];
                    } else {
                        proxyUrl = splitUrl[0];
                    }
                    //alert( "Remove Tag 2 : " + proxyUrl );

                    var updatedSourceStore = new CQ.Ext.data.Store({
                        url: proxyUrl,
                        reader: reader
                    });

                    updatedSourceStore.load();
                    firstGrid.reconfigure( updatedSourceStore, columnModel );
                }

            },
            title: 'Tag Properties',
            name: './tags',
            x: 250,
            y: 0,
            width: 400
        });
        CQ.form.GridtoGrid.superclass.constructor.call(this, config);

        this.selectionForm = new CQ.Ext.Panel({
            name         : this.name,
            width        : 'auto',
            height       : 600,
            border       : false,
            layout       : {
                type: 'absolute',
                padding: 0
            },
            items        : [
                tagInputField,
                firstGrid,
                secondGrid
            ]
        });
        this.add(this.selectionForm);

    },
    submitPanel: function() {

        var store = secondGrid.getStore();
        var storeData = store.data;
        var storeFields = store.fields;
        var params = {};

        if( storeData.length > 0 ){
            for(var i = 0; i < storeData.length; i++) {
                for(var j = 0; j < storeFields.length; j++){
                    var name = storeFields.item(j).name;
                    if (!params[name]) {
                        params[name] = new Array();
                    }

                    if( name == 'headline' ){
                        var headline = storeData.get(i).get(name);
                        if( headline.indexOf ( '_' ) == headline.length - 1 ){
                            params[name] = params[name].concat( storeData.get(i).get(name));
                        } else {
                            params[name] = params[name].concat( storeData.get(i).get(name) + "_");
                        }
                    } else {
                        params[name] = params[name].concat( storeData.get(i).get(name));
                    }
                }
            }
        }

        var serverResponse = CQ.utils.HTTP.post(this.urlPost, null, params, this);
        return CQ.utils.HTTP.isOk(serverResponse);
    },
    loadContent: function() {

        loadStore = null;

        if (!this.content) {
            var url = CQ.HTTP.externalize(this.urlGet);
            loadStore = new CQ.data.SlingStore({
                url: url+'.infinity.json'
            });
        } else if (this.content instanceof CQ.Ext.data.Store) {
            loadStore = this.content;
        }

        loadStore.load({
            callback: this.processRecords,
            scope: this
        });

    },
    processRecords: function(){

        var tagString = loadStore.getAt(0).get('tags');
        var sourceProxyUrl = firstGrid.getStore().proxy.url;

        if( tagString != undefined && sourceProxyUrl.indexOf( tagString ) == -1 ){
            sourceProxyUrl = sourceProxyUrl + "?tags=" + tagString;
        }
        //alert( "Process Records: " + sourceProxyUrl );
        var updatedSourceStore = new CQ.Ext.data.Store({
            url: sourceProxyUrl,
            reader: reader
        });

        updatedSourceStore.load();
        firstGrid.reconfigure( updatedSourceStore, columnModel );

        var tempHeadlineData = loadStore.getAt(0).get('headline');
        if( tempHeadlineData != undefined ){
            tempHeadlineData = "" + tempHeadlineData;
            var headlineData = new Array();
            headlineData = tempHeadlineData.split('_,');

            var tempPathData = loadStore.getAt(0).get('path');
            var tempDateData = loadStore.getAt(0).get('date');

            var secondGridData = [];

            for( var i = 0; i < headlineData.length; i++ ){
                var headline = headlineData[i].replace('_', '');
                secondGridData.push({headline: headline, path: tempPathData[i], date: tempDateData[i]});
            }

            //destination store
            var destinationStore = new CQ.Ext.data.Store({
                reader: reader
            });

            destinationStore.loadData( secondGridData );

            secondGrid.reconfigure( destinationStore, columnModel );
        }

    }
});
CQ.Ext.reg("gridtogrid", CQ.form.GridtoGrid);

2. Create a component in CQ and in the dialog add a widget node with the following properties.

Screen Shot 2013-10-04 at 12.18.59 PM

  • xtype : gridtogrid –> this matches to what you register the widget as in the javascript.
  • dataSrcUrl –> This is the URL which returns a JSON object that is consumed by the JSONReader in the widget JS
  • jsonReaderConfig –> Configuration for the JSON reader object
    • example: [{name: 'headline', mapping: 'headline'},{name:'date', mapping:'date'}]
  • columnModelConfig –> Configuration for the column model to be used in the left grid.
    • example: [{header: 'Headline', width: 500, sortable: false, dataIndex: 'headline'},{header: 'Date', width: 200, sortable: false, dataIndex: 'date', hidden: true}]
  • sortField –> The name of the field that the data should be sorted on
  • sortDirection –> ASC/DESC
  • name –> name of property on the content node to store the destination list under. It will stored as a String[].

Screen Shot 2013-10-04 at 12.28.30 PM

3. Add the component on your page and double click to open the dialog. You should now see a list on the left and can populate the right based on the capabilities described above (double clicks).

Screen Shot 2013-10-04 at 12.39.13 PM

 

 

4. An example servlet that returns a JSON object to be consumed by the widget above is below

final ValueMap attributes = resource.adaptTo(ValueMap.class);
try{
TidyJSONWriter writer = new TidyJSONWriter(out);
String headline = "Headline Not Set";
Calendar date = null;
String stringDate = null;
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
String path = null;

String tagsParameter = request.getParameter( "tags" );

if( tagsParameter != null ){
String[] tags = tagsParameter.split( "," );

NodeIterator articleList = Headlines.getArticleListByTags( currentNode.getSession(), tags, 25 );

if( articleList != null && articleList.getSize() > 0 ){
writer.array();
for( NodeIterator ni = articleList; articleList.hasNext(); ){
Node article = articleList.nextNode();

//Get Headline
try{
headline = article.getProperty( "headline" ).getValue().getString();
} catch (Exception e){
log.info( "headline doesn't exist, using static value instead" );
}

//Get Date
Node contentNode = currentNode.getSession().getNode( article.getPath().replace( "/contentpar/articleBody", "" ) );
try{
date = contentNode.getProperty( "cq:lastModified" ).getValue().getDate();
} catch (Exception e){
log.info( "cq:lastModified doesn't exist, using jcr:created instead" );
date = contentNode.getProperty( "jcr:created" ).getValue().getDate();
}

//Convert date to String
if( date != null ){
stringDate = sdf.format( date.getTime() );
} else {
log.info( "Date Object was null" );
}

//Get Path
path = article.getPath().replace("/jcr:content/contentpar/articleBody","");
writer.object();
writer.key("headline").value( headline );
writer.key("path").value( path );
writer.key("date").value(stringDate);
writer.endObject();
}
writer.endArray();
}

} else {
NodeIterator articleList = Headlines.getArticleListByModifiedDate( currentNode.getSession(), 25 );

if( articleList != null && articleList.getSize() > 0 ){
writer.array();
for( NodeIterator ni = articleList; articleList.hasNext(); ){
Node article = articleList.nextNode();
Node bodyNode = currentNode.getSession().getNode( article.getPath() + "/contentpar/articleBody" );

//Get Headline
try{
headline = bodyNode.getProperty("headline").getValue().getString();
} catch( Exception e ){
log.info( "headline doesn't exist, using static value instead" );
}

//Get lastModifiedDate
try{
date = article.getProperty( "cq:lastModified" ).getValue().getDate();
} catch (Exception e){
log.info( "cq:lastModified doesn't exist, using jcr:created instead" );
date = article.getProperty( "jcr:created" ).getValue().getDate();
}

if( date != null ){
stringDate = sdf.format( date.getTime() );
} else {
log.info( "Date Object was null" );
}

//Get Path
path = bodyNode.getPath().replace("/jcr:content/contentpar/articleBody","");

writer.object();
writer.key("headline").value( headline );
writer.key("path").value( path );
writer.key("date").value(stringDate);
writer.endObject();

}
writer.endArray();
}
}

} catch (Exception e){
log.info( e.getMessage() );
}

I can post a video of the widget in action if this blog post isn’t descriptive  enough. As always, please post comments/questions and I will try to get back to you as soon as possible.

12:46 PM Permalink
May 17, 2013

Firewall rules for typical CQ Author-Publish-Dispatcher setup

I came across a few projects in the past that required CQ implementations spanning multiple data centers thus needing tons of firewall rules. Like many enterprise environments, the process of filing firewall requests (to open a few holes), to scheduling outage windows and all the way to getting the rules in place, can easily take up a week or two, if not days. Not to mention that the same process probably have to be repeated multiple times (DEV, QA, Staging, Production…). This is especially hard if your organization is using CQ for the very first time, as there are rules usually missed.

So I wanted to use this post to capture the port that should be opened in a typical CQ set up. In the following tables, I’m assuming:

  • CQ Author running on default port 4502
  • CQ Publish running on default port 4503
  • Dispatcher/Webserver running on port 80/443.

Replication from CQ Author to CQ Publish

+---------------+-------------+--------------------+------------------+
| Source Server | Source Port | Destination Server | Destination Port |
+---------------+-------------+--------------------+------------------+
| CQ Author     | 4502        | CQ Publish         | 4503             |
+---------------+-------------+--------------------+------------------+

Cache Flushing from CQ Publish to Dispatcher

+---------------+-------------+--------------------+------------------+
| Source Server | Source Port | Destination Server | Destination Port |
+---------------+-------------+--------------------+------------------+
| CQ Publish    | 4503        | Dispatcher         | 80 / 443         |
+---------------+-------------+--------------------+------------------+

Clustering CQ (2 instances)

+-----------------+-------------+--------------------+------------------+
| Source Server   | Source Port | Destination Server | Destination Port |
+-----------------+-------------+--------------------+------------------+
| CQ (Auth1/Pub1) | 8088        | CQ (Auth2/Pub2)    | 8088             |
+-----------------+-------------+--------------------+------------------+

Range of ports listed at http://dev.day.com/docs/en/cq/current/core/administering/cluster.html

Reverse Replication from CQ Publish to CQ Author

+---------------+-------------+--------------------+------------------+
| Source Server | Source Port | Destination Server | Destination Port |
+---------------+-------------+--------------------+------------------+
| CQ Publish    | 4503        | CQ Author          | 4502             |
+---------------+-------------+--------------------+------------------+

Dispatcher retrieving published content

+---------------+-------------+--------------------+------------------+
| Source Server | Source Port | Destination Server | Destination Port |
+---------------+-------------+--------------------+------------------+
| CQ Dispatcher | 80 / 443    | CQ Publish         | 4503             |
+---------------+-------------+--------------------+------------------+

If there is a dispatcher sitting in front of Clustered Author

+-------------------+-------------+--------------------+------------------+
| Source Server     | Source Port | Destination Server | Destination Port |
+-------------------+-------------+--------------------+------------------+
| CQ Author         | 4502        | Author Dispatcher  | 80 / 443         |
+-------------------+-------------+--------------------+------------------+
| Author Dispatcher | 80 / 443    | CQ Author          | 4502             |
+-------------------+-------------+--------------------+------------------+
| CQ Author1        | 8088        | CQ Author2         | 8088             |
+-------------------+-------------+--------------------+------------------+

And finally, there are also services being integrated. An example would be port 389 for LDAP / Active Directory. Don’t forget those!

9:35 PM Permalink