KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > portal > transformation > ProxyTransformer


1 /*
2  * Copyright 1999-2002,2004-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.portal.transformation;
17
18 import java.io.BufferedInputStream JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.PrintWriter JavaDoc;
22 import java.io.StringWriter JavaDoc;
23 import java.io.UnsupportedEncodingException JavaDoc;
24 import java.net.HttpURLConnection JavaDoc;
25 import java.net.MalformedURLException JavaDoc;
26 import java.net.URL JavaDoc;
27 import java.util.Enumeration JavaDoc;
28 import java.util.Map JavaDoc;
29
30 import org.apache.avalon.framework.parameters.ParameterException;
31 import org.apache.avalon.framework.parameters.Parameterizable;
32 import org.apache.avalon.framework.parameters.Parameters;
33 import org.apache.avalon.framework.service.ServiceException;
34 import org.apache.avalon.framework.service.ServiceManager;
35 import org.apache.avalon.framework.service.Serviceable;
36 import org.apache.cocoon.ProcessingException;
37 import org.apache.cocoon.environment.ObjectModelHelper;
38 import org.apache.cocoon.environment.Request;
39 import org.apache.cocoon.environment.SourceResolver;
40 import org.apache.cocoon.portal.Constants;
41 import org.apache.cocoon.portal.PortalService;
42 import org.apache.cocoon.portal.coplet.CopletData;
43 import org.apache.cocoon.portal.coplet.CopletInstanceData;
44 import org.apache.cocoon.portal.profile.ProfileManager;
45 import org.apache.cocoon.transformation.AbstractTransformer;
46 import org.apache.cocoon.util.NetUtils;
47 import org.apache.cocoon.xml.XMLUtils;
48 import org.apache.cocoon.xml.dom.DOMStreamer;
49 import org.w3c.dom.Document JavaDoc;
50 import org.w3c.dom.Element JavaDoc;
51 import org.w3c.dom.NodeList JavaDoc;
52 import org.w3c.tidy.Configuration;
53 import org.w3c.tidy.Tidy;
54 import org.xml.sax.Attributes JavaDoc;
55 import org.xml.sax.SAXException JavaDoc;
56
57 /**
58  * This transformer is used to insert the XHTML data from an request
59  * to an external application at the specified element ("envelope-tag" parameter).
60  * Nesessary connection data for the external request like sessionid, cookies,
61  * documentbase, the uri, etc. will be taken from the application coplet instance
62  * data.
63  * @author <a HREF="mailto:friedrich.klenner@rzb.at">Friedrich Klenner</a>
64  * @author <a HREF="mailto:gernot.koller@rizit.at">Gernot Koller</a>
65  *
66  * @version CVS $Id: ProxyTransformer.java 329454 2005-10-29 17:44:45Z cziegeler $
67  */

68 public class ProxyTransformer
69     extends AbstractTransformer
70     implements Serviceable, Parameterizable {
71
72     /**
73      * Parameter for specifying the envelope tag
74      */

75     public static final String JavaDoc ENVELOPE_TAG_PARAMETER = "envelope-tag";
76
77     public static final String JavaDoc PORTALNAME = "cocoon-portal-portalname";
78     public static final String JavaDoc COPLETID = "cocoon-portal-copletid";
79     public static final String JavaDoc PROXY_PREFIX = "proxy-";
80
81     public static final String JavaDoc COPLET_ID_PARAM = "copletId";
82     public static final String JavaDoc PORTAL_NAME_PARAM = "portalName";
83
84     // Coplet instance data keys
85
public static final String JavaDoc SESSIONTOKEN = "sessiontoken";
86     public static final String JavaDoc COOKIE = "cookie";
87     public static final String JavaDoc START_URI = "start-uri";
88     public static final String JavaDoc LINK = "link";
89     public static final String JavaDoc DOCUMENT_BASE = "documentbase";
90
91     /**
92      * Parameter for specifying the java protocol handler (used for https)
93      */

94     public static final String JavaDoc PROTOCOL_HANDLER_PARAMETER = "protocol-handler";
95
96     /**
97      * The document base uri
98      */

99     protected String JavaDoc documentBase;
100
101     /**
102      * The current link to the external application
103      */

104     protected String JavaDoc link;
105
106     /**
107      * The default value for the envelope Tag
108      */

109     protected String JavaDoc defaultEnvelopeTag;
110     
111     /**
112      * This tag will include the external XHMTL
113      */

114     protected String JavaDoc envelopeTag;
115
116     /**
117      * The Avalon component manager
118      */

119     protected ServiceManager manager;
120
121     /**
122      * The coplet instance data
123      */

124     protected CopletInstanceData copletInstanceData;
125
126     /**
127      * The original request to the portal
128      */

129     protected Request request;
130
131     /**
132      * The encoding (JTidy constant) if configured
133      */

134     protected int configuredEncoding;
135
136     /**
137      * The user agent identification string if confiugured
138      */

139     protected String JavaDoc userAgent;
140
141     /** The sitemap parameters */
142     protected Parameters parameters;
143
144     /* (non-Javadoc)
145      * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
146      */

147     public void service(ServiceManager manager) throws ServiceException {
148         this.manager = manager;
149
150     }
151
152     /**
153      * For the proxy transformer the envelope-tag parameter can be specified.
154      * @see org.apache.avalon.framework.parameters.Parameterizable#parameterize(Parameters)
155      */

156     public void parameterize(Parameters parameters) {
157         this.defaultEnvelopeTag = parameters.getParameter(ENVELOPE_TAG_PARAMETER, null);
158     }
159
160     /**
161      * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(SourceResolver, Map, String, Parameters)
162      */

163     public void setup(SourceResolver resolver,
164                       Map JavaDoc objectModel,
165                       String JavaDoc src,
166                       Parameters parameters)
167     throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
168         this.parameters = parameters;
169         this.request = ObjectModelHelper.getRequest(objectModel);
170
171         this.copletInstanceData = getInstanceData(this.manager, objectModel, parameters);
172
173         final CopletData copletData = this.copletInstanceData.getCopletData();
174
175         final String JavaDoc startURI = (String JavaDoc)copletData.getAttribute(START_URI);
176
177         this.link = (String JavaDoc) this.copletInstanceData.getTemporaryAttribute(LINK);
178
179         this.documentBase = (String JavaDoc) this.copletInstanceData.getAttribute(DOCUMENT_BASE);
180
181         if (this.link == null) {
182             this.link = startURI;
183         }
184
185         if (documentBase == null) {
186             this.documentBase = this.link.substring(0, this.link.lastIndexOf('/') + 1);
187             copletInstanceData.setAttribute(DOCUMENT_BASE, this.documentBase);
188         }
189
190         this.configuredEncoding = encodingConstantFromString((String JavaDoc)copletData.getAttribute("encoding"));
191         this.userAgent = (String JavaDoc)copletData.getAttribute("user-agent");
192         this.envelopeTag = parameters.getParameter(ENVELOPE_TAG_PARAMETER, this.defaultEnvelopeTag);
193
194         if (envelopeTag == null) {
195             throw new ProcessingException("Can not initialize ProxyTransformer - sitemap parameter 'envelope-tag' missing");
196         }
197     }
198
199     /* (non-Javadoc)
200      * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
201      */

202     public void recycle() {
203         super.recycle();
204         this.envelopeTag = null;
205         this.userAgent = null;
206         this.documentBase = null;
207         this.link = null;
208         this.request = null;
209         this.parameters = null;
210         this.copletInstanceData = null;
211     }
212
213     /**
214      * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
215      */

216     public void startElement(
217         String JavaDoc uri,
218         String JavaDoc name,
219         String JavaDoc raw,
220         Attributes JavaDoc attributes)
221         throws SAXException JavaDoc {
222         super.startElement(uri, name, raw, attributes);
223
224         if (name.equalsIgnoreCase(this.envelopeTag)) {
225             //super.startElement(uri, name, raw, attributes);
226
processRequest();
227             //super.endElement(uri, name, raw);
228
}
229     }
230
231     /**
232      * Processes the request to the external application
233      */

234     protected void processRequest() throws SAXException JavaDoc {
235         try {
236             String JavaDoc remoteURI = null;
237             try {
238                 remoteURI = resolveURI(link, documentBase);
239             } catch (MalformedURLException JavaDoc ex) {
240                 throw new SAXException JavaDoc(ex);
241             }
242             boolean firstparameter = true;
243             boolean post = ("POST".equals(request.getMethod()));
244             int pos = remoteURI.indexOf('?');
245             final StringBuffer JavaDoc query = new StringBuffer JavaDoc();
246             if ( pos != -1 ) {
247                 if ( !post ) {
248                     query.append('?');
249                 }
250                 query.append(remoteURI.substring(pos+1));
251                 firstparameter = true;
252                 remoteURI = remoteURI.substring(0, pos);
253             }
254
255             // append all parameters of the current request, except those where
256
// the name of the request parameter starts with "cocoon-portal-"
257
final Enumeration JavaDoc enumeration = request.getParameterNames();
258             while (enumeration.hasMoreElements()) {
259                 String JavaDoc paramName = (String JavaDoc) enumeration.nextElement();
260
261                 if (!paramName.startsWith("cocoon-portal-")) {
262                     String JavaDoc[] paramValues = request.getParameterValues(paramName);
263                     for (int i = 0; i < paramValues.length; i++) {
264                         firstparameter = this.appendParameter(query, firstparameter, post, paramName, paramValues[i]);
265                     }
266                 }
267             }
268             
269             // now append parameters from the sitemap - if any
270
final String JavaDoc[] names = this.parameters.getNames();
271             for(int i=0; i<names.length; i++) {
272                 if ( names[i].startsWith("add:") ) {
273                     final String JavaDoc value = this.parameters.getParameter(names[i]);
274                     if ( value != null && value.trim().length() > 0 ) {
275                         final String JavaDoc pName = names[i].substring(4);
276                         firstparameter = this.appendParameter(query, firstparameter, post, pName, value.trim());
277                     }
278                 }
279                 
280             }
281
282             Document JavaDoc result = null;
283             try {
284                 do {
285                     if ( this.getLogger().isDebugEnabled() ) {
286                         this.getLogger().debug("Invoking '" + remoteURI + query.toString() +"', post="+post);
287                     }
288                     HttpURLConnection JavaDoc connection =
289                         connect(request, remoteURI, query.toString(), post);
290                     remoteURI = checkForRedirect(connection, documentBase);
291
292                     if (remoteURI == null) {
293                         result = readXML(connection);
294                         remoteURI = checkForRedirect(result, documentBase);
295                     }
296
297                 }
298                 while (remoteURI != null);
299             } catch (IOException JavaDoc ex) {
300                 throw new SAXException JavaDoc(
301                     "Failed to retrieve remoteURI " + remoteURI,
302                     ex);
303             }
304
305             XMLUtils.stripDuplicateAttributes(result, null);
306
307             DOMStreamer streamer = new DOMStreamer();
308             streamer.setContentHandler(contentHandler);
309             streamer.stream(result.getDocumentElement());
310         } catch (SAXException JavaDoc se) {
311             throw se;
312         } catch (Exception JavaDoc ex) {
313             throw new SAXException JavaDoc(ex);
314         }
315     }
316
317     protected boolean appendParameter(StringBuffer JavaDoc buffer,
318                                       boolean firstparameter,
319                                       boolean post,
320                                       String JavaDoc name,
321                                       String JavaDoc value)
322     throws UnsupportedEncodingException JavaDoc {
323         if (firstparameter) {
324             if (!post) {
325                 buffer.append('?');
326             }
327             firstparameter = false;
328         } else {
329             buffer.append('&');
330         }
331
332         buffer.append(NetUtils.encode(name, "utf-8"));
333         buffer.append('=');
334         buffer.append(NetUtils.encode(value, "utf-8"));
335
336         return firstparameter;
337     }
338     /**
339      * Check the http status code of the http response to detect any redirects.
340      * @param connection The HttpURLConnection
341      * @param documentBase The current documentBase (needed for relative redirects)
342      * @return the redirected URL or null if no redirects are detected.
343      * @throws IOException if exceptions occure while analysing the response
344      */

345     protected String JavaDoc checkForRedirect(
346         HttpURLConnection JavaDoc connection,
347         String JavaDoc documentBase)
348         throws IOException JavaDoc {
349
350         if (connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM
351             || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) {
352
353             String JavaDoc newURI = (connection.getHeaderField("location"));
354
355             int index_semikolon = newURI.indexOf(";");
356             int index_question = newURI.indexOf("?");
357
358             if ((index_semikolon > -1)) {
359                 String JavaDoc sessionToken =
360                     newURI.substring(
361                         index_semikolon + 1,
362                         (index_question == -1
363                             ? newURI.length()
364                             : index_question));
365                 this.copletInstanceData.getPersistentAspectData().put(
366                     SESSIONTOKEN,
367                     sessionToken);
368             }
369             newURI = resolveURI(newURI, documentBase);
370             return newURI;
371         }
372         return null;
373     }
374
375     /**
376      * Analyses the XHTML response document for redirects in &lt;meta http-equiv="refresh"&gt; elements.
377      * @param doc The W3C DOM document containing the XHTML response
378      * @param documentBase The current document base (needed for relative redirects)
379      * @return String the redirected URL or null if no redirects are detected.
380      * @throws MalformedURLException if the redirect uri is malformed.
381      */

382     protected String JavaDoc checkForRedirect(Document JavaDoc doc, String JavaDoc documentBase)
383         throws MalformedURLException JavaDoc {
384         Element JavaDoc htmlElement = doc.getDocumentElement();
385         NodeList JavaDoc headList = htmlElement.getElementsByTagName("head");
386         if (headList.getLength() <= 0) {
387             return null;
388         }
389
390         Element JavaDoc headElement = (Element JavaDoc) headList.item(0);
391         NodeList JavaDoc metaList = headElement.getElementsByTagName("meta");
392         for (int i = 0; i < metaList.getLength(); i++) {
393             Element JavaDoc metaElement = (Element JavaDoc) metaList.item(i);
394             String JavaDoc httpEquiv = metaElement.getAttribute("http-equiv");
395             if ("refresh".equalsIgnoreCase(httpEquiv)) {
396                 String JavaDoc content = metaElement.getAttribute("content");
397                 if (content != null) {
398                     String JavaDoc time =
399                         content.substring(0, content.indexOf(';'));
400                     try {
401                         if (Integer.parseInt(time) > 10) {
402                             getLogger().warn(
403                                 "Redirects with refresh time longer than 10 seconds ("
404                                     + time
405                                     + " seconds) will be ignored!");
406                             return null;
407                         }
408                     }
409                     catch (NumberFormatException JavaDoc ex) {
410                         getLogger().warn(
411                             "Failed to convert refresh time from redirect to integer: "
412                                 + time);
413                         return null;
414                     }
415
416                     String JavaDoc newURI =
417                         content.substring(content.indexOf('=') + 1);
418
419                     int index_semikolon = newURI.indexOf(";");
420                     int index_question = newURI.indexOf("?");
421
422                     if ((index_semikolon > -1)) {
423                         String JavaDoc sessionToken =
424                             newURI.substring(
425                                 index_semikolon + 1,
426                                 (index_question == -1
427                                     ? newURI.length()
428                                     : index_question));
429                         this.copletInstanceData.getPersistentAspectData().put(
430                             SESSIONTOKEN,
431                             sessionToken);
432                     }
433                     newURI = resolveURI(newURI, documentBase);
434                     return newURI;
435                 }
436             }
437         }
438         return null;
439     }
440
441     /**
442      * Reads the HTML document from given connection and returns a correct W3C DOM XHTML document
443      * @param connection hte HttpURLConnection to read from
444      * @return the result as valid W3C DOM XHTML document
445      */

446     protected Document JavaDoc readXML(HttpURLConnection JavaDoc connection)
447     throws SAXException JavaDoc {
448         try {
449             int charEncoding = configuredEncoding;
450
451             String JavaDoc contentType = connection.getHeaderField("Content-Type");
452             int begin = contentType.indexOf("charset=");
453             int end = -1;
454             if (begin > -1) {
455                 begin += "charset=".length();
456                 end = contentType.indexOf(';', begin);
457                 if (end == -1) {
458                     end = contentType.length();
459                 }
460                 String JavaDoc charset = contentType.substring(begin, end);
461                 charEncoding = encodingConstantFromString(charset);
462             }
463
464             InputStream JavaDoc stream = connection.getInputStream();
465             // Setup an instance of Tidy.
466
Tidy tidy = new Tidy();
467             tidy.setXmlOut(true);
468
469             tidy.setCharEncoding(charEncoding);
470             tidy.setXHTML(true);
471
472             //Set Jtidy warnings on-off
473
tidy.setShowWarnings(this.getLogger().isWarnEnabled());
474             //Set Jtidy final result summary on-off
475
tidy.setQuiet(!this.getLogger().isInfoEnabled());
476             //Set Jtidy infos to a String (will be logged) instead of System.out
477
StringWriter JavaDoc stringWriter = new StringWriter JavaDoc();
478             //FIXME ??
479
PrintWriter JavaDoc errorWriter = new PrintWriter JavaDoc(stringWriter);
480             tidy.setErrout(errorWriter);
481             // Extract the document using JTidy and stream it.
482
Document JavaDoc doc = tidy.parseDOM(new BufferedInputStream JavaDoc(stream), null);
483             errorWriter.flush();
484             errorWriter.close();
485             return doc;
486         } catch (Exception JavaDoc ex) {
487             throw new SAXException JavaDoc(ex);
488         }
489     }
490
491     /**
492      * Helper method to convert the HTTP encoding String to JTidy encoding constants.
493      * @param encoding the HTTP encoding String
494      * @return the corresponding JTidy constant.
495      */

496     private int encodingConstantFromString(String JavaDoc encoding) {
497         if ("ISO8859_1".equalsIgnoreCase(encoding)) {
498             return Configuration.LATIN1;
499         }
500         else if ("UTF-8".equalsIgnoreCase(encoding)) {
501             return Configuration.UTF8;
502         }
503         else {
504             return Configuration.LATIN1;
505         }
506     }
507
508     /**
509      * Establish the HttpURLConnection to the given uri.
510      * User-Agent, Accept-Language and Encoding headers will be copied from the original
511      * request, if no other headers are specified.
512      * @param request the original request
513      * @param uri the remote uri
514      * @param query the remote query string
515      * @param post true if request method was POST
516      * @return the established HttpURLConnection
517      * @throws IOException on any exception
518      */

519     protected HttpURLConnection JavaDoc connect(
520         Request request,
521         String JavaDoc uri,
522         String JavaDoc query,
523         boolean post)
524         throws IOException JavaDoc {
525
526         String JavaDoc cookie = (String JavaDoc) copletInstanceData.getAttribute(COOKIE);
527
528         if (!post) {
529             uri = uri + query;
530         }
531
532         URL JavaDoc url = new URL JavaDoc(uri);
533
534         HttpURLConnection JavaDoc connection = (HttpURLConnection JavaDoc) url.openConnection();
535
536         connection.setInstanceFollowRedirects(false);
537
538         connection.setRequestMethod(request.getMethod());
539         connection.setRequestProperty(
540             "User-Agent",
541             (userAgent != null) ? userAgent : request.getHeader("User-Agent"));
542
543         connection.setRequestProperty(
544             "Accept-Language",
545             request.getHeader("Accept-Language"));
546
547         if (cookie != null) {
548             connection.setRequestProperty(COOKIE, cookie);
549         }
550
551         if (post) {
552             connection.setDoOutput(true);
553             connection.setRequestProperty(
554                 "Content-Type",
555                 "application/x-www-form-urlencoded");
556             connection.setRequestProperty(
557                 "Content-Length",
558                 String.valueOf(query.length()));
559         }
560
561         connection.connect();
562
563         if (post) {
564             PrintWriter JavaDoc out = new PrintWriter JavaDoc(connection.getOutputStream());
565             out.print(query);
566             out.close();
567         }
568
569         copletInstanceData.setAttribute(
570             COOKIE,
571             connection.getHeaderField(COOKIE));
572         documentBase = uri.substring(0, uri.lastIndexOf('/') + 1);
573         copletInstanceData.setAttribute(DOCUMENT_BASE, documentBase);
574         return connection;
575     }
576
577     /**
578     * Resolve the possibly relative uri to an absolue uri based on given document base.
579     * @param uri the uri to resolve
580     * @param documentBase the current document base
581     * @return returns an absolute URI based on document base (e.g. http://mydomain.com/some/file.html)
582     * @throws MalformedURLException if uri or document base is malformed.
583     */

584     public static String JavaDoc resolveURI(String JavaDoc uri, String JavaDoc documentBase)
585     throws MalformedURLException JavaDoc {
586
587         if (uri == null) {
588             throw new IllegalArgumentException JavaDoc("URI to be resolved must not be null!");
589         }
590
591         if (uri.indexOf("://") > -1) {
592             return uri;
593         }
594
595         if (documentBase == null) {
596             throw new IllegalArgumentException JavaDoc("Documentbase String must not be null!");
597         }
598
599         //cut ./ from uri
600
if (uri.startsWith("./")) {
601             uri = uri.substring(2);
602         }
603
604         URL JavaDoc documentBaseURL = new URL JavaDoc(documentBase);
605
606         //absolute uri
607
if (uri.startsWith("/")) {
608             return documentBaseURL.getProtocol()
609                 + "://"
610                 + documentBaseURL.getAuthority()
611                 + uri;
612         }
613         return documentBaseURL.toExternalForm() + uri;
614     }
615
616     public static CopletInstanceData getInstanceData(ServiceManager manager,
617                                                      String JavaDoc copletID,
618                                                      String JavaDoc portalName)
619     throws ProcessingException {
620         PortalService portalService = null;
621         try {
622             portalService = (PortalService) manager.lookup(PortalService.ROLE);
623                 
624             ProfileManager profileManager = portalService.getComponentManager().getProfileManager();
625             CopletInstanceData data = profileManager.getCopletInstanceData(copletID);
626             return data;
627         } catch (ServiceException e) {
628             throw new ProcessingException("Error getting portal service.", e);
629         } finally {
630             manager.release(portalService);
631         }
632     }
633
634     /**
635     * Method getInstanceData.
636     * @param manager
637     * @param objectModel
638     * @param parameters
639     * @return CopletInstanceData
640     * @throws ProcessingException
641     */

642     public static CopletInstanceData getInstanceData(ServiceManager manager,
643                                                      Map JavaDoc objectModel,
644                                                      Parameters parameters)
645     throws ProcessingException {
646
647         PortalService portalService = null;
648         try {
649             portalService = (PortalService) manager.lookup(PortalService.ROLE);
650
651             // determine coplet id
652
String JavaDoc copletId = null;
653             Map JavaDoc context = (Map JavaDoc) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
654             if (context != null) {
655                 copletId = (String JavaDoc) context.get(Constants.COPLET_ID_KEY);
656                 if (copletId == null) {
657                     throw new ProcessingException("copletId must be passed as parameter or in the object model within the parent context.");
658                 }
659             } else {
660                 try {
661                     copletId = parameters.getParameter(COPLET_ID_PARAM);
662
663                 } catch (ParameterException e) {
664                     throw new ProcessingException("copletId and portalName must be passed as parameter or in the object model within the parent context.");
665                 }
666             }
667             return portalService.getComponentManager().getProfileManager().getCopletInstanceData(copletId);
668         } catch (ServiceException e) {
669             throw new ProcessingException("Error getting portal service.", e);
670         } finally {
671             manager.release(portalService);
672         }
673     }
674
675 }
676
Popular Tags