Skip to content

Instantly share code, notes, and snippets.

@rgriffith
Created January 24, 2014 19:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rgriffith/28ca51a9f24d6d285d21 to your computer and use it in GitHub Desktop.
Save rgriffith/28ca51a9f24d6d285d21 to your computer and use it in GitHub Desktop.
Sample Publish Trigger that reads back a published page from an FTP or SFTP server and puts it into a docx directory on the server.
package com.cms.publish.client;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.log4j.Logger;
import com.cms.publish.PublishTrigger;
import com.cms.publish.PublishTriggerEntityTypes;
import com.cms.publish.PublishTriggerException;
import com.cms.publish.PublishTriggerInformation;
import com.enterprisedt.net.ftp.FTPClient;
import com.enterprisedt.net.ftp.FTPTransferType;
import com.hannonhill.cascade.api.asset.admin.Destination;
import com.hannonhill.cascade.api.asset.admin.FTPTransport;
import com.hannonhill.cascade.api.asset.admin.Target;
import com.hannonhill.cascade.api.asset.admin.Transport;
import com.hannonhill.cascade.api.asset.common.BaseAsset;
import com.hannonhill.cascade.api.asset.common.Identifier;
import com.hannonhill.cascade.api.asset.common.PageConfiguration;
import com.hannonhill.cascade.api.asset.home.Page;
import com.hannonhill.cascade.api.operation.Read;
import com.hannonhill.cascade.api.operation.result.ReadOperationResult;
import com.hannonhill.cascade.model.dom.identifier.EntityType;
import com.hannonhill.cascade.model.dom.identifier.EntityTypes;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
/**
* This trigger reads back the published page from the FTP or SFTP server and puts it into the docx directory
* on the server.
* Then, the docx directory is converted into a .docx file and sent back to the FTP or SFTP server.
* The docx directory must be downloaded and saved into cascade server /WEB-INF/classes/com/cms/publish/client
* folder.
*
* This trigger was originally written for Juilliard and integrated into the Cascade Server
* codebase on September 10, 2008.
*
* @author Artur Tomusiak
* @version $Id$
* @since 5.7
*/
public class DocxPublishTrigger implements PublishTrigger
{
private PublishTriggerInformation information; // information about currently published asset
private ZipOutputStream cpZipOutputStream = null; // used by the zip method
private String strSource = ""; // path to the directory that should be zipped
private String strTarget = ""; // path to a zip file that should be created
private final String currentFolderWithJAR = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
private final String currentFolderBeforeFix = currentFolderWithJAR.substring(0, currentFolderWithJAR.lastIndexOf('/') + 1);
private final String currentFolder = currentFolderBeforeFix.charAt(2) == ':' ? currentFolderBeforeFix.substring(1) : currentFolderBeforeFix;
private static final String DESTINATION_NAME = "Word Open XML"; // The name of destination for which this
// trigger should react
private static final String DOCX_PATH2 = "docx/"; // Path to the template folder. Don't use "../"
private final String docxPath = currentFolder + DOCX_PATH2;
private final String wordPath = docxPath + "openxml_word_template/word/"; // path to the folder, in which
// document.xml file should be
private static final Logger LOG = Logger.getLogger(DocxPublishTrigger.class);
/**
* Default constructor
*/
public DocxPublishTrigger()
{
super();
}
public void setPublishInformation(PublishTriggerInformation information)
{
this.information = information;
}
/**
* An identifier
*
* @author Artur Tomusiak
*/
private class IdentifierImpl implements Identifier
{
private final String id;
private final EntityType type;
public IdentifierImpl(String id, EntityType type)
{
this.id = id;
this.type = type;
}
public String getId()
{
return id;
}
public EntityType getType()
{
return type;
}
}
/**
* This method gets executed every time Cascade Server publishes a file or page
* If it is a page and its publishing currently using destination "Word Open XML",
* it will load back the published file, put it into the word folder,
* zip the openxml_word_template folder contents and upload back the zipped archive
*/
public void invoke() throws PublishTriggerException
{
if (information.getEntityType() == PublishTriggerEntityTypes.TYPE_PAGE && information.getDestinationName().equals(DESTINATION_NAME))
{
LOG.debug("********************************");
LOG.debug("DOC PUBLISH TRIGGER START");
LOG.debug("Destination: " + information.getDestinationName());
LOG.debug("Page: " + information.getEntityPath());
LOG.debug("Page ID: " + information.getEntityId());
LOG.debug("Page Configuration ID: " + information.getPageConfigurationId());
try
{
Transport transport = (Transport) readAsset(information.getTransportId(), EntityTypes.TYPE_TRANSPORT);
if (transport instanceof FTPTransport)
{
FTPTransport ftpTransport = (FTPTransport) transport;
Destination destination = (Destination) readAsset(information.getDestinationId(), EntityTypes.TYPE_DESTINATION);
Page page = (Page) readAsset(information.getEntityId(), EntityTypes.TYPE_PAGE);
// Start logic that calculates the path of the published file on the server
String targetId = information.getTargetId();
String serverName = ftpTransport.getServerName();
int port = ftpTransport.getPort();
String username = ftpTransport.getUserName();
String password = ftpTransport.getPassword();
String destinationDirectory = destination.getDirectory();
String transportDirectory = ftpTransport.getInitialDirectory();
String transportDestinationDirectory = getTransportDestination(transportDirectory, destinationDirectory);
String ftpFileName = page.getName();
String ftpFolder = transportDestinationDirectory;
if (!isEmptyString(targetId))
{
Target target = (Target) readAsset(targetId, EntityTypes.TYPE_TARGET);
String targetPath = information.getTargetPath();
boolean removeBaseFolder = target.isRemoveBaseFolderFromPubPath();
boolean includeTargetPath = target.isShouldIncludeTargetPath();
ftpFileName += target.getExtension();
ftpFolder += (includeTargetPath ? addSlash(targetPath) : "");
ftpFolder += addSlash(removeBaseFolder(page.getParentFolder().getPath(), target.getBaseFolder().getPath(), removeBaseFolder));
}
else
{
ftpFolder += addSlash(page.getParentFolder().getPath());
PageConfiguration pageConfiguration = (PageConfiguration) readAsset(information.getPageConfigurationId(),
EntityTypes.TYPE_PAGECONFIGURATION);
ftpFileName += pageConfiguration.getExtension();
}
String ftpPath = ftpFolder + "/" + ftpFileName;
String localPath = wordPath + ftpFileName;
// End logic that calculates the path of the published file on the server
boolean downloaded;
// Download the published file
if (ftpTransport.isSecure())
downloaded = downloadSftpFile(serverName, port, username, password, ftpPath, localPath);
else
downloaded = downloadFtpFile(serverName, port, username, password, ftpPath, localPath);
if (!downloaded)
LOG.debug("Error downloading file from the ftp server");
else
{
// if it's not already document.xml, delete old document.xml and
// rename our file to document.xml
if (!ftpFileName.equals("document.xml"))
{
// Deleting old document.xml file
System.gc();// workaround to fix java bug that doesn't allow to delete files that
// were created by java
boolean deleted = (new java.io.File(wordPath + "document.xml")).delete();
if (!deleted)
LOG.debug("Could not delete the file");
// Renaming document.xml
java.io.File f = new java.io.File(wordPath + ftpFileName);
f.renameTo(new java.io.File(wordPath + "document.xml"));
}
// Zipping openxml_word_template to .docx
strSource = docxPath + "openxml_word_template";
strTarget = docxPath + page.getName() + ".docx";
zip();
// upload the zipped .docx file
String ftpFileNew = page.getName() + ".docx";
String ftpPathNew = ftpFolder + "/" + ftpFileNew;
boolean uploaded;
if (ftpTransport.isSecure())
uploaded = uploadSftpFile(serverName, port, username, password, strTarget, ftpPathNew);
else
uploaded = uploadFtpFile(serverName, port, username, password, strTarget, ftpPathNew);
if (!uploaded)
LOG.debug("Error uploading file to the ftp server");
else
{
// Delete the local .docx file
System.gc();// workaround to fix java bug that doesn't allow to delete files that
// were created by java
boolean deleted = (new java.io.File(docxPath + page.getName() + ".docx")).delete();
if (!deleted)
LOG.debug("Could not delete the file .docx file");
}
}
}
else
{
LOG.debug("Error: The trigger works only for the FTP and SFTP transports");
}
}
catch (Exception e)
{
LOG.error(e.getMessage(), e);
}
LOG.debug("DOC PUBLISH TRIGGER END");
LOG.debug("********************************");
}
}
/**
* Reads an asset from CascadeServer using the Read operation API
*
* @param id
* @param type
* @return
* @throws Exception
*/
private BaseAsset readAsset(String id, EntityType type) throws Exception
{
Read read = new Read();
Identifier toRead = new IdentifierImpl(id, type);
read.setToRead(toRead);
read.setUsername("system");
ReadOperationResult result = (ReadOperationResult) read.perform();
return result.getAsset();
}
/**
* Takes the transport directory and destination directory and combines them together. If they match (for
* example transport
* directory is "/cascade" and destination directory is "/cascade/something"), then it will return only
* the destination
* directory ("/cascade/something"). If they don't match, they will be simply combined.
*
* @param transport The transport directory
* @param destination The destination directory
* @return The combined directory
*/
private String getTransportDestination(String transport, String destination)
{
if (destination.length() >= transport.length() && transport.equals(destination.substring(0, transport.length())))
return removeSlashAtTheEnd(destination);
return removeSlashAtTheEnd(transport) + addSlash(destination);
}
/**
* Removes a slash from the end if it is there
*
* @param path
* @return
*/
private String removeSlashAtTheEnd(String path)
{
if (path.length() > 0)
{
if (path.charAt(path.length() - 1) == '/')
return path.substring(0, path.length() - 1);
return path;
}
return path;
}
/**
* Adds a forward slash to the front of the path if necessary and removes
* the forward slash from the end. If the path is an empty string, it will return an empty string.
*
* @param path
* @return
*/
private String addSlash(String path)
{
path = removeSlashAtTheEnd(path);
if (path.length() > 0)
{
if (path.charAt(0) == '/')
return path;
return "/" + path;
}
return path;
}
/**
* Returns a string that is the "path" without the "baseFolderPath". Works only if "shouldRemove" is true.
* If "shouldRemove" is false,
* it will return back the "path".
*
* @param path
* @param baseFolderPath
* @param shouldRemove
* @return
*/
private String removeBaseFolder(String path, String baseFolderPath, boolean shouldRemove)
{
String bigPath = addSlash(path);
String smallPath = addSlash(baseFolderPath);
if ((shouldRemove) && (bigPath.length() >= smallPath.length()))
return bigPath.substring(smallPath.length());
return path;
}
/**
* Downloads a file from an SFTP server.
*
* @param serverName
* @param port
* @param username
* @param password
* @param fromPath
* @param toPath
* @return True if download successful. False if didn't download.
*/
private boolean downloadSftpFile(String serverName, int port, String username, String password, String fromPath, String toPath)
{
return transferSftpFile(serverName, port, username, password, fromPath, toPath, true);
}
/**
* Uploads a file to an SFTP server.
*
* @param serverName
* @param port
* @param username
* @param password
* @param fromPath
* @param toPath
* @return True if upload successful. False if didn't upload.
*/
private boolean uploadSftpFile(String serverName, int port, String username, String password, String fromPath, String toPath)
{
return transferSftpFile(serverName, port, username, password, fromPath, toPath, false);
}
/**
* Either downloads or uploads a file to/from an SFTP server.
*
* @param serverName
* @param port
* @param username
* @param password
* @param fromPath
* @param toPath
* @param download
* @return True if download/upload successful. False if didn't download/upload.
*/
private boolean transferSftpFile(String serverName, int port, String username, String password, String fromPath, String toPath, boolean download)
{
try
{
JSch sshClient = new JSch();
Session session = sshClient.getSession(username, serverName, port);
session.setConfig("StrictHostKeyChecking", "no");
session.setTimeout(15000);
session.setPassword(password);
session.connect();
ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
channel.connect();
if (download)
{
LOG.debug("Downloading From SFTP");
LOG.debug("To Path: " + toPath);
LOG.debug("From Path: " + fromPath);
channel.get(fromPath, toPath);
}
else
{
LOG.debug("Uploading To SFTP");
LOG.debug("To Path: " + toPath);
LOG.debug("From Path: " + fromPath);
channel.put(fromPath, toPath);
}
if (!channel.isClosed())
channel.quit();
if (!session.isConnected())
session.disconnect();
return true;
}
catch (Exception e)
{
LOG.error(e.getMessage(), e);
return false;
}
}
/**
* Downloads a file from an FTP server.
*
* @param serverName
* @param port
* @param username
* @param password
* @param fromPath
* @param toPath
* @return True if download successful. False if didn't download.
*/
private boolean downloadFtpFile(String serverName, int port, String username, String password, String fromPath, String toPath)
{
return transferFtpFile(serverName, port, username, password, fromPath, toPath, true);
}
/**
* Uploads a file to an FTP server.
*
* @param serverName
* @param port
* @param username
* @param password
* @param fromPath
* @param toPath
* @return True if upload successful. False if didn't upload.
*/
private boolean uploadFtpFile(String serverName, int port, String username, String password, String fromPath, String toPath)
{
return transferFtpFile(serverName, port, username, password, fromPath, toPath, false);
}
/**
* Either downloads or uploads a file to/from an FTP server.
*
* @param serverName
* @param port
* @param username
* @param password
* @param fromPath
* @param toPath
* @param download
* @return True if download/upload successful. False if didn't download/upload.
*/
private boolean transferFtpFile(String serverName, int port, String username, String password, String fromPath, String toPath, boolean download)
{
FTPClient client = new FTPClient();
try
{
client.setRemoteHost(serverName);
client.setRemotePort(port);
client.connect();
client.login(username, password);
client.setType(FTPTransferType.BINARY);
if (download)
{
LOG.debug("Downloading From FTP");
LOG.debug("To Path: " + toPath);
LOG.debug("From Path: " + fromPath);
client.get(toPath, fromPath);
return true;
}
LOG.debug("Uploading To FTP");
LOG.debug("To Path: " + toPath);
LOG.debug("From Path: " + fromPath);
client.put(fromPath, toPath);
return true;
}
catch (Exception e)
{
LOG.error("Error occurred: " + e.getMessage(), e);
return false;
}
finally
{
try
{
client.quit();
}
catch (Exception e)
{
LOG.debug("Error occurred when quitting the client: " + e.getMessage(), e);
}
}
}
/**
* Zips a file/folder with the path "strSource". The zipped file has path "strTarget"
*/
private void zip()
{
try
{
java.io.File cpFile = new java.io.File(strSource);
if (!cpFile.isFile() && !cpFile.isDirectory())
{
LOG.debug("Zipper: Source file/directory Not Found!");
return;
}
cpZipOutputStream = new ZipOutputStream(new FileOutputStream(strTarget));
cpZipOutputStream.setLevel(0);
zipFiles(cpFile);
cpZipOutputStream.finish();
cpZipOutputStream.close();
LOG.debug("Finished creating zip file " + strTarget + " from source " + strSource);
}
catch (Exception e)
{
LOG.error(e.getMessage(), e);
}
}
/**
* Zips the given {@link java.io.File}.
*
* @param cpFile
*/
private void zipFiles(java.io.File cpFile)
{
if (cpFile.isDirectory())
{
java.io.File[] fList = cpFile.listFiles();
for (int i = 0; i < fList.length; i++)
{
zipFiles(fList[i]);
}
}
else
{
String strAbsPath = cpFile.getAbsolutePath();
String strZipEntryName = strAbsPath.substring(strSource.length() + 1);
byte[] b = new byte[(int) (cpFile.length())];
FileInputStream cpFileInputStream = null;
try
{
cpFileInputStream = new FileInputStream(cpFile);
cpFileInputStream.read(b, 0, (int) cpFile.length());
ZipEntry cpZipEntry = new ZipEntry(strZipEntryName);
cpZipOutputStream.putNextEntry(cpZipEntry);
cpZipOutputStream.write(b, 0, (int) cpFile.length());
cpZipOutputStream.closeEntry();
}
catch (Exception e)
{
LOG.error(e.getMessage(), e);
}
finally
{
try
{
if (cpFileInputStream != null)
cpFileInputStream.close();
}
catch (Exception e)
{
LOG.error("Error closing input stream for file '" + cpFile + "': " + e.getMessage(), e);
}
}
}
}
public void setParameter(String name, String value)
{
LOG.debug("setParameter: name: " + name + " value: " + value);
}
/**
* Returns true if the string is empty or null
*
* @param s
* @return
*/
private boolean isEmptyString(String s)
{
return s == null || s.equals("");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment