/*
* Copyright 2000-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.portals.bridges.perl;
import javax.portlet.GenericPortlet;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletURL;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.PortletSession;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jetspeed.rewriter.JetspeedRewriterController;
import org.apache.jetspeed.rewriter.RewriterController;
import org.apache.jetspeed.rewriter.RewriterException;
import org.apache.jetspeed.rewriter.RulesetRewriter;
import org.apache.jetspeed.rewriter.html.SwingParserAdaptor;
import org.apache.jetspeed.rewriter.rules.Ruleset;
import org.apache.jetspeed.rewriter.xml.SaxParserAdaptor;
/**
* This portlet is executes a Perl/cgi files in a portlet.
*
* Note:
* The Perl Portlet uses the rewriter component that requires config xml files.
* Make sre that the portlet application using the Perl Portlet has the following files included
* in WEB-INF/conf: rewriter-rules-mapping.xml and default-rewriter-rules.xml
*
* @author <a href="mailto:rogerrut@apache.org">Roger Ruttimann</a>
* @version $Id: PerlPortlet.java,v 1.7 2005/03/21 17:31:11 rogerrut Exp $
*/
public class PerlPortlet extends GenericPortlet {
/**
* INIT parameters required by the Perl Portlet:PerlScript, ScriptPath, DemoMode
*
* Name of the scrip to to execute
*/
public static final String PARAM_PERL_SCRIPT = "PerlScript";
/**
* Name of the Script Path where the perl scripts (among others) are located
*/
public static final String PARAM_SCRIPT_PATH = "ScriptPath";
/**
* DemoMode on or off
*/
public static final String PARAM_DEMO_MODE = "DemoMode";
/**
* PARAM_APPLICATION
*
* ApplicationName identifies the caller so that the portlet only refreshes
* content that was supposed for the portlet.
* If the application name is undefined the portlet will process the request.
* If you have more than one perl-portlet for the same user/session all the portlets
* will be refreshed with the same content.
*/
public static final String PARAM_APPLICATION = "Application";
// Local variables
private String perlScript = "perl-demo.cgi";
private String scriptPath = "cgi-bin";
private String applicationName = null;
// Switch that shows basic information about the perl script to run
private boolean bDemoMode = false;
private static final Log log = LogFactory.getLog(PerlPortlet.class);
// caching status -- cache the last query
private String lastQuery = null;
// Cache the last generated page
String lastPage = null;
/* PerlContent rewriter */
RulesetRewriter rewriter = null;
RewriterController rewriteController = null;
/** Default encoding */
public String defaultEncoding = "iso-8859-1";
public void init(PortletConfig config) throws PortletException
{
super.init(config);
// Get the INIT PARAMETERS for this portlet. If the values are missing
// throw an exception
scriptPath = config.getInitParameter(PARAM_SCRIPT_PATH);
perlScript = config.getInitParameter(PARAM_PERL_SCRIPT);
String demoMode = config.getInitParameter(PARAM_DEMO_MODE);
applicationName = config.getInitParameter(PARAM_APPLICATION);
if (demoMode != null && demoMode.compareToIgnoreCase("on") == 0)
bDemoMode = true;
if (scriptPath == null)
throw new PortletException("Portlet " + config.getPortletName()
+ " is incorrectly configured. Init parameter "
+ PARAM_SCRIPT_PATH + " not specified");
if (perlScript == null)
throw new PortletException("Portlet " + config.getPortletName()
+ " is incorrectly configured. Init parameter "
+ PARAM_PERL_SCRIPT + " not specified");
}
/**
* processAction()
* Checks action initiated by the perl portlet (invoking other perl scripts)
* @param actionRequest
* @param actionResponse
* @throws PortletException
* @throws IOException
*/
public void processAction(ActionRequest actionRequest, ActionResponse actionResponse) throws PortletException, IOException
{
String perlParameter = actionRequest.getParameter(PerlParameters.ACTION_PARAMETER_PERL);
//TODO: Remove Debug
System.out.println("Action parameter for perl " + perlParameter);
/*
* If the perlParameter is not empty create a PerlParameter object and attach it to the session
*/
if ( perlParameter != null && perlParameter.length() > 0)
{
// Perl Parameter Object
PerlParameters cgi = new PerlParameters();
cgi.setApplicationName(this.applicationName);
// Separate the values before and after the Query Mark ?
int ixQuery = perlParameter.indexOf('?');
if ( ixQuery != -1)
{
cgi.setScriptName(perlParameter.substring(0,ixQuery));
String queryArguments = perlParameter.substring(ixQuery+1);
System.out.println("ProcessRequest -- Script " + perlParameter.substring(0,ixQuery) + " Query string " + queryArguments);
int ixQuerySeparator = queryArguments.indexOf('&');
while ( ixQuerySeparator != -1)
{
cgi.addQueryArgument(queryArguments.substring(0, ixQuerySeparator));
queryArguments = queryArguments.substring(ixQuerySeparator+1);
ixQuerySeparator = queryArguments.indexOf('&');
}
cgi.addQueryArgument(queryArguments);
// Add the PerlParameters to the session
actionRequest.getPortletSession().setAttribute(PerlParameters.PERL_PARAMETER, cgi, PortletSession.APPLICATION_SCOPE);
}
else
{
// No query string just the script name
cgi.setScriptName(perlParameter);
// Get all the parameters from the request and add them as query arguments
Enumeration names = actionRequest.getParameterNames();
String name, value;
while (names.hasMoreElements())
{
name = (String)names.nextElement();
// PERL_PARAMETER already processed just ignore it
if (name.compareToIgnoreCase(PerlParameters.ACTION_PARAMETER_PERL) != 0)
{
value = actionRequest.getParameter(name);
// TODO: Remove debug
System.out.println("Query: " + name + "=" + value );
cgi.addQueryArgument(name + "=" + value);
}
}
// Add the PerlParameters to the session
actionRequest.getPortletSession().setAttribute(PerlParameters.PERL_PARAMETER, cgi, PortletSession.APPLICATION_SCOPE);
}
}
}
/**
* doView
* Executes the perl script that is defined by the property PerlScript.
* If the incoming request has the query perl-script= defined this script will be executed.
*/
public void doView(RenderRequest request, RenderResponse response)
throws PortletException, IOException
{
// TODO: Remove debug
System.out.println("Path info for request " + ((HttpServletRequest)((HttpServletRequestWrapper) request).getRequest()).getPathInfo());
// Set the content type
response.setContentType("text/html");
// Get a writer object that can be used to generate the output
HttpServletResponse httpResponse = (HttpServletResponse)((HttpServletResponseWrapper) response).getResponse();
//PrintWriter writer = response.getWriter();
PrintWriter writer = httpResponse.getWriter();
String query = null;
String scriptName = null;
PerlParameters perlParam = null;
/**
* The Perl parameters are either passed by a session attribute (invoked from a different portlet) or as an action which is replaced
* with a session while processing actions..
*/
try
{
perlParam = (PerlParameters)request.getPortletSession().getAttribute(PerlParameters.PERL_PARAMETER, PortletSession.APPLICATION_SCOPE);
}
catch (Exception e )
{
perlParam = null;
}
if (perlParam != null)
{
// Only use the values if the call is designated to this script
if (perlParam.getApplicationName().compareToIgnoreCase(this.applicationName) == 0)
{
query = perlParam.getQueryString();
perlScript = perlParam.getScriptName();
}
if (this.applicationName == null ) // not yet initialized
{
this.applicationName = perlParam.getApplicationName();
}
else
{
// If the application name doesn't match just use the cached version and return
if ( lastPage != null // has run at least once
&& this.applicationName != null // No filtering runs for any perl request
&& perlParam.getApplicationName().compareToIgnoreCase(this.applicationName) != 0)
{
// Use cache
writer.println(this.lastPage);
return;
}
}
}
// Open the perl script and extract the perl executable path. It's the same way as apache HTTP executes PERL
String perlExecutable = null;
PortletContext portletApplication = getPortletContext();
String path = portletApplication.getRealPath("/WEB-INF");
String contextPath = path + "/";
String fullScriptPath = contextPath + scriptPath;
// Build full path to scripts
if (perlScript.startsWith("/") == false )
fullScriptPath += "/";
fullScriptPath += perlScript;
// command to execute
String command = null;
// Open the script and read the first line to get the executable !/usr/bin/perl OR !c:\bin\perl\perl.exe
try
{
BufferedReader in= new BufferedReader(new FileReader(fullScriptPath));
String lnExecutable = in.readLine();
if (lnExecutable != null )
{
// Make sure that the executable is defined -- could be a compiled cgi with no executable defined
String lnExecutableLower = lnExecutable.toLowerCase();
int px = lnExecutableLower.indexOf("perl");
int ix = lnExecutable.indexOf('!');
if ( ix != -1 && px != -1 )
{
int ex = lnExecutable.indexOf(' ',ix);
if ( ex >= 0 )
{
perlExecutable = lnExecutable.substring(ix+1, ex);
}
else
{
perlExecutable = lnExecutable.substring(ix+1);
}
}
}
//Close file
in.close();
StringBuffer commandBuffer = new StringBuffer();
if (perlExecutable == null)
commandBuffer.append(fullScriptPath);
else
commandBuffer.append(perlExecutable).append(' ').append(fullScriptPath);
command = new String(commandBuffer.toString());
}
catch(FileNotFoundException e)
{
writer.println("<P><B>File doesn't exist (" + fullScriptPath + ")</B></P>");
}
catch(IOException e)
{
writer.println("<P><B>IO Exception (" + e.getMessage() + ")</B></P>");
}
catch(Exception e)
{
writer.println("<P><B>IO Exception (" + e.getMessage() + ")</B></P>");
}
String envQuery = "QUERY_STRING=" + query ;
String[] env = null;
env = new String[]{"REQUEST_METHOD=GET", envQuery};
if ( bDemoMode == true)
{
// Script info. This is for the Demo only
writer.println("<P>The portlet executes the perl script defined by the init-params. If you don't get an output make sure that the perl executable defined in the script is valid.");
writer.println("The executable is defined on the first line of your script.</P>Examples<ul><li><B>UNIX/Linux:</B>!/usr/bin/perl</li><li><B>Windows:</B>!c:\\bin\\perl\\perl.exe</li></ul>");
writer.println("<B><P>Perl Script:</B>" + fullScriptPath + "<BR>");
writer.println("<B>Perl executable:</B>" + perlExecutable + "<BR>");
writer.println("<B>Query String:</B>" + query + "</P>");
}
//Execute the perl script from the command line
if (command != null )
{
// Execute command in a separate process. The perl output is written to the stdout
try
{
// Start process
Process proc = Runtime.getRuntime().exec(command,env);
// Get stdout of process and create a buffered reader
InputStream in = proc.getInputStream();
BufferedReader perlResult = new BufferedReader(new InputStreamReader(in));
StringBuffer page = new StringBuffer();
//Wait until proc is done
boolean bProcDone = false;
while (bProcDone == false)
{
try
{
proc.exitValue() ;
bProcDone = true;
}
catch(IllegalThreadStateException e)
{
bProcDone = false; //Not done yet
// Read the buffer otherwise the process will be blocked because it can't write to the stdout (max size of buffer)
int ln;
while ((ln = perlResult.read()) != -1)
{
char c = (char)ln;
if (c != '\n' && c != '\r')
page.append((char)ln);
}
}
}
// Perl execution done read the remaining buffer
int ln = -1;
while ((ln = perlResult.read()) != -1)
{
char c = (char)ln;
if (c != '\n' && c != '\r')
page.append((char)ln);
}
// Close stream
perlResult.close();
/*
* Use rewriter for replacing all URL's with portlet actions
*/
if (rewriteController == null)
{
try
{
// Create rewriter adaptor
rewriteController = getController(contextPath);
}
catch (Exception e)
{
// Failed to create rewriter controller
throw new PortletException("WebContentProtlet failed to create rewriter controller. Error:"
+ e.getMessage());
}
}
// Any HREFs and Form actions should be extended with the ActionURL
PortletURL actionURL = response.createActionURL();
byte[] content = this.doWebContent(page, actionURL, PerlParameters.ACTION_PARAMETER_PERL);
ByteArrayInputStream bais = new ByteArrayInputStream(content);
OutputStream oswriter = response.getPortletOutputStream();
int BLOCK_SIZE = 4096;
byte[] bytes = new byte[BLOCK_SIZE];
try
{
int length = bais.read(bytes);
while (length != -1)
{
if (length != 0)
{
oswriter.write(bytes, 0, length);
}
length = bais.read(bytes);
}
}
finally
{
bytes = null;
}
// Cache page
lastPage = new String(content);
/*
// Post Process for generated page
ScriptPostProcess processor = new ScriptPostProcess();
processor.setInitalPage(page);
processor.postProcessPage(actionURL, PerlParameters.ACTION_PARAMETER_PERL);
String finalPage = processor.getFinalizedPage();
// Write the page
writer.println(finalPage);
// Cache page
lastPage = new String(finalPage);
*/
}
catch(IOException ioe)
{
writer.println("<P><B>Exception while reading perl output" + ioe.getMessage() + "</B></P>");
}
}
}
/*
* Generate a rewrite controller using the basic rules file
*/
private RewriterController getController(String contextPath) throws Exception
{
Class[] rewriterClasses = new Class[]
{ PerlContentRewriter.class, PerlContentRewriter.class};
Class[] adaptorClasses = new Class[]
{ SwingParserAdaptor.class, SaxParserAdaptor.class};
RewriterController rwc = new JetspeedRewriterController(contextPath + "conf/rewriter-rules-mapping.xml", Arrays
.asList(rewriterClasses), Arrays.asList(adaptorClasses));
FileReader reader = new FileReader(contextPath + "conf/default-rewriter-rules.xml");
Ruleset ruleset = rwc.loadRuleset(reader);
reader.close();
rewriter = rwc.createRewriter(ruleset);
return rwc;
}
protected byte[] doWebContent(StringBuffer perlRenderedPage, PortletURL actionURL, String actionParameterName)
throws PortletException
{
// Initialization
Writer htmlWriter = null;
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
try
{
htmlWriter = new OutputStreamWriter(byteOutputStream, this.defaultEncoding);
// Set the action URL in the rewriter
((PerlContentRewriter) rewriter).setActionURL(actionURL);
((PerlContentRewriter) rewriter).setActionParameterName(actionParameterName);
StringReader perlReader = new StringReader(perlRenderedPage.toString());
rewriter.rewrite(rewriteController.createParserAdaptor("text/html"), perlReader, htmlWriter);
htmlWriter.flush();
}
catch (UnsupportedEncodingException ueex)
{
throw new PortletException("Encoding " + defaultEncoding + " not supported. Error: " + ueex.getMessage());
}
catch (RewriterException rwe)
{
throw new PortletException("Failed to rewrite Perl ouput. Error: " + rwe.getMessage());
}
catch (Exception e)
{
throw new PortletException("Exception while rewritting Perl output. Error: " + e.getMessage());
}
return byteOutputStream.toByteArray();
}
}