1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.util.descriptor;
18
19 import java.io.BufferedInputStream;
20 import java.io.File;
21 import java.io.FileFilter;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
30 import java.io.OutputStreamWriter;
31 import java.io.Reader;
32 import java.io.UnsupportedEncodingException;
33 import java.io.Writer;
34 import java.net.MalformedURLException;
35 import java.net.URL;
36 import java.net.URLClassLoader;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.Iterator;
40 import java.util.List;
41
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.jetspeed.Jetspeed;
45 import org.apache.jetspeed.om.common.portlet.MutablePortletApplication;
46 import org.apache.jetspeed.om.common.servlet.MutableWebApplication;
47 import org.apache.jetspeed.tools.deploy.JetspeedWebApplicationRewriter;
48 import org.apache.jetspeed.tools.deploy.JetspeedWebApplicationRewriterFactory;
49 import org.apache.jetspeed.tools.pamanager.PortletApplicationException;
50 import org.apache.jetspeed.util.DirectoryHelper;
51 import org.apache.jetspeed.util.FileSystemHelper;
52 import org.apache.jetspeed.util.MultiFileChecksumHelper;
53 import org.apache.pluto.om.common.SecurityRoleRef;
54 import org.apache.pluto.om.common.SecurityRoleRefSet;
55 import org.apache.pluto.om.common.SecurityRoleSet;
56 import org.apache.pluto.om.portlet.PortletDefinition;
57 import org.jdom.Document;
58 import org.jdom.input.SAXBuilder;
59 import org.jdom.output.Format;
60 import org.jdom.output.XMLOutputter;
61 import org.xml.sax.EntityResolver;
62 import org.xml.sax.InputSource;
63 import org.xml.sax.SAXException;
64
65 /***
66 * <p>
67 * This class facilitates operations a portlet applications WAR file or WAR
68 * file-like structure.
69 * </p>
70 * <p>
71 * This class is utility class used mainly implementors of
72 * {@link org.apache.jetspeed.pamanager.Deployment}and
73 * {@link org.apache.jetspeed.pamanager.Registration}to assist in deployment
74 * and undeployment of portlet applications.
75 *
76 * @author <a href="mailto:sweaver@einnovation.com">Scott T. Weaver </a>
77 * @author <a href="mailto:mavery@einnovation.com">Matt Avery </a>
78 * @version $Id: PortletApplicationWar.java,v 1.10 2004/07/06 16:56:19 weaver
79 * Exp $
80 */
81 public class PortletApplicationWar
82 {
83 protected static final String WEB_XML_STRING =
84 "<?xml version='1.0' encoding='ISO-8859-1'?>" +
85 "<!DOCTYPE web-app " +
86 "PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' " +
87 "'http://java.sun.com/dtd/web-app_2_3.dtd'>\n" +
88 "<web-app></web-app>";
89
90 public static final String PORTLET_XML_PATH = "WEB-INF/portlet.xml";
91 public static final String WEB_XML_PATH = "WEB-INF/web.xml";
92 public static final String EXTENDED_PORTLET_XML_PATH = "WEB-INF/jetspeed-portlet.xml";
93
94 protected static final int MAX_BUFFER_SIZE = 1024;
95
96 public static final String JETSPEED_SERVLET_XPATH = "/web-app/servlet/servlet-name[contains(child::text(), \"JetspeedContainer\")]";
97 public static final String JETSPEED_SERVLET_MAPPING_XPATH = "/web-app/servlet-mapping/servlet-name[contains(child::text(), \"JetspeedContainer\")]";
98
99 protected static final Log log = LogFactory.getLog("deployment");
100
101 protected String paName;
102 protected String webAppContextRoot;
103 protected FileSystemHelper warStruct;
104 private MutableWebApplication webApp;
105 private MutablePortletApplication portletApp;
106 private long paChecksum;
107 protected final List openedResources;
108
109 protected static final String[] ELEMENTS_BEFORE_SERVLET = new String[]{"icon", "display-name", "description",
110 "distributable", "context-param", "filter", "filter-mapping", "listener", "servlet"};
111 protected static final String[] ELEMENTS_BEFORE_SERVLET_MAPPING = new String[]{"icon", "display-name",
112 "description", "distributable", "context-param", "filter", "filter-mapping", "listener", "servlet",
113 "servlet-mapping"};
114
115 /***
116 * @param warFile
117 * {@link org.apache.jetspeed.util.FileSystemHelper}representing
118 * the WAR file we are working with. This
119 * <code>FileSystemHelper</code> can be an actual WAR file or a
120 * directory structure layed out in a WAR-like fashion. name of
121 * the portlet application the <code>warPath</code> contains
122 * @param webAppContextRoot
123 * context root relative to the servlet container of this app
124 */
125 public PortletApplicationWar( FileSystemHelper warStruct, String paName, String webAppContextRoot )
126 {
127 this(warStruct, paName, webAppContextRoot, 0);
128 }
129
130 public PortletApplicationWar( FileSystemHelper warStruct, String paName, String webAppContextRoot, long paChecksum )
131 {
132 validatePortletApplicationName(paName);
133
134 this.paName = paName;
135 this.webAppContextRoot = webAppContextRoot;
136 this.openedResources = new ArrayList();
137 this.warStruct = warStruct;
138 this.paChecksum = paChecksum;
139 }
140
141 public long getPortletApplicationChecksum() throws IOException
142 {
143 if ( this.paChecksum == 0)
144 {
145 this.paChecksum = MultiFileChecksumHelper.getChecksum(new File[] {
146 new File(warStruct.getRootDirectory(), WEB_XML_PATH),
147 new File(warStruct.getRootDirectory(), PORTLET_XML_PATH),
148 new File(warStruct.getRootDirectory(), EXTENDED_PORTLET_XML_PATH) });
149 }
150 if (this.paChecksum == 0)
151 {
152 throw new IOException("Cannot find any deployment descriptor for Portlet Application "+paName);
153 }
154 return paChecksum;
155 }
156
157 /***
158 * <p>
159 * validatePortletApplicationName
160 * </p>
161 *
162 * @param paName
163 */
164 private void validatePortletApplicationName( String paName )
165 {
166 if (paName == null || paName.startsWith("/") || paName.startsWith("//") || paName.endsWith("/")
167 || paName.endsWith("//"))
168 {
169 throw new IllegalStateException("Invalid paName \"" + paName
170 + "\". paName cannot be null nor can it begin nor end with any slashes.");
171 }
172 }
173
174 /***
175 *
176 * <p>
177 * createWebApp
178 * </p>
179 * Creates a web applicaiton object based on the values in this WAR's
180 * WEB-INF/web.xml
181 *
182 * @return @throws
183 * PortletApplicationException
184 * @throws IOException
185 * @see org.apache.jetspeed.util.descriptor.WebApplicationDescriptor
186 */
187 public MutableWebApplication createWebApp() throws PortletApplicationException, IOException
188 {
189 Reader webXmlReader = getReader(WEB_XML_PATH);
190
191 try
192 {
193 WebApplicationDescriptor webAppDescriptor = new WebApplicationDescriptor(webXmlReader, webAppContextRoot);
194 webApp = webAppDescriptor.createWebApplication();
195 return webApp;
196 }
197
198 finally
199 {
200 try
201 {
202 if (webXmlReader != null)
203 {
204 webXmlReader.close();
205 }
206 }
207 catch (IOException e1)
208 {
209 e1.printStackTrace();
210 }
211 }
212
213 }
214
215 /***
216 *
217 * <p>
218 * createPortletApp
219 * </p>
220 * Creates a portlet application object based of the WAR file's
221 * WEB-INF/portlet.xml
222 *
223 * @return @throws
224 * PortletApplicationException
225 * @throws IOException
226 * @see org.apache.jetspeed.uitl.descriptor.PortletApplicationDescriptor
227 */
228 public MutablePortletApplication createPortletApp(ClassLoader classLoader) throws PortletApplicationException, IOException
229 {
230 Reader portletXmlReader = getReader(PORTLET_XML_PATH);
231
232 try
233 {
234 PortletApplicationDescriptor paDescriptor = new PortletApplicationDescriptor(portletXmlReader, paName);
235 portletApp = paDescriptor.createPortletApplication(classLoader);
236
237 Reader extMetaDataXml = null;
238 try
239 {
240 extMetaDataXml = getReader(EXTENDED_PORTLET_XML_PATH);
241 if (extMetaDataXml != null)
242 {
243 ExtendedPortletMetadata extMetaData = new ExtendedPortletMetadata(extMetaDataXml, portletApp);
244 extMetaData.load();
245 }
246 }
247 catch (IOException e)
248 {
249 if ( e instanceof FileNotFoundException )
250 {
251 log.info("No extended metadata found.");
252 }
253 else
254 {
255 throw new PortletApplicationException("Failed to load existing metadata.",e);
256 }
257 }
258 catch (MetaDataException e)
259 {
260 throw new PortletApplicationException("Failed to load existing metadata.", e);
261 }
262 finally
263 {
264 if (null != extMetaDataXml)
265 {
266 extMetaDataXml.close();
267 }
268 }
269 portletApp.setChecksum(paChecksum);
270 return portletApp;
271 }
272 finally
273 {
274 if (portletXmlReader != null)
275 {
276 portletXmlReader.close();
277 }
278 }
279 }
280
281 public MutablePortletApplication createPortletApp()
282 throws PortletApplicationException, IOException
283 {
284 return createPortletApp(this.getClass().getClassLoader());
285 }
286
287 /***
288 *
289 * <p>
290 * getReader
291 * </p>
292 * Returns a <code>java.io.Reader</code> to a resource within this WAR's
293 * structure.
294 *
295 * @param path
296 * realtive to an object within this WAR's file structure
297 * @return java.io.Reader to the file within the WAR
298 * @throws IOException
299 * if the path does not exist or there was a problem reading the
300 * WAR.
301 *
302 */
303 protected Reader getReader( String path ) throws IOException
304 {
305 BufferedInputStream is = new BufferedInputStream(getInputStream(path));
306
307 String enc = "UTF-8";
308 try
309 {
310 is.mark(MAX_BUFFER_SIZE);
311 byte[] buf = new byte[MAX_BUFFER_SIZE];
312 int size = is.read(buf, 0, MAX_BUFFER_SIZE);
313 if (size > 0)
314 {
315 String key = "encoding=\"";
316 String data = new String(buf, 0, size, "US-ASCII");
317 int lb = data.indexOf("\n");
318 if (lb > 0)
319 {
320 data = data.substring(0, lb);
321 }
322 int off = data.indexOf(key);
323 if (off > 0)
324 {
325 enc = data.substring(off + key.length(), data.indexOf('"', off + key.length()));
326 }
327 }
328 }
329 catch (UnsupportedEncodingException e)
330 {
331 log.warn("Unsupported encoding.", e);
332 }
333 catch (IOException e)
334 {
335 log.warn("Unsupported encoding.", e);
336 }
337
338
339 is.reset();
340 return new InputStreamReader(is, enc);
341 }
342
343 /***
344 *
345 * <p>
346 * getInputStream
347 * </p>
348 *
349 * Returns a <code>java.io.InputStream</code> to a resource within this
350 * WAR's structure.
351 *
352 * @param path
353 * realtive to an object within this WAR's file structure
354 * @return java.io.InputStream to the file within the WAR
355 * @throws IOException
356 * if the path does not exist or there was a problem reading the
357 * WAR.
358 */
359 protected InputStream getInputStream( String path ) throws IOException
360 {
361 File child = new File(warStruct.getRootDirectory(), path);
362 if (child == null || !child.exists())
363 {
364 throw new FileNotFoundException("Unable to locate file or path " + child);
365 }
366
367 FileInputStream fileInputStream = new FileInputStream(child);
368 openedResources.add(fileInputStream);
369 return fileInputStream;
370 }
371
372 /***
373 *
374 * <p>
375 * getOutputStream
376 * </p>
377 *
378 * Returns a <code>java.io.OutputStream</code> to a resource within this
379 * WAR's structure.
380 *
381 * @param path
382 * realtive to an object within this WAR's file structure
383 * @return java.io.Reader to the file within the WAR
384 * @throws IOException
385 * if the path does not exist or there was a problem reading the
386 * WAR.
387 */
388 protected OutputStream getOutputStream( String path ) throws IOException
389 {
390 File child = new File(warStruct.getRootDirectory(), path);
391 if (child == null)
392 {
393 throw new FileNotFoundException("Unable to locate file or path " + child);
394 }
395 FileOutputStream fileOutputStream = new FileOutputStream(child);
396 openedResources.add(fileOutputStream);
397 return fileOutputStream;
398 }
399
400 protected Writer getWriter( String path ) throws IOException
401 {
402 return new OutputStreamWriter(getOutputStream(path));
403 }
404
405 /***
406 *
407 * <p>
408 * copyWar
409 * </p>
410 * Copies the entire WAR structure to the path defined in
411 * <code>targetAppRoot</code>
412 *
413 * @param targetAppRoot
414 * target to copy this WAR's content to. If the path ends in
415 * <code>.war</code> or <code>.jar</code>. The war will be
416 * copied into that file in jar format.
417 * @return PortletApplicationWar representing the newly created WAR.
418 * @throws IOException
419 */
420 public PortletApplicationWar copyWar( String targetAppRoot ) throws IOException
421 {
422
423
424 FileSystemHelper target = new DirectoryHelper(new File(targetAppRoot));
425 try
426 {
427 target.copyFrom(warStruct.getRootDirectory());
428
429 return new PortletApplicationWar(target, paName, webAppContextRoot, paChecksum);
430
431 }
432 catch (IOException e)
433 {
434 throw e;
435 }
436 finally
437 {
438 target.close();
439
440 }
441 }
442
443 /***
444 *
445 * <p>
446 * removeWar
447 * </p>
448 * Deletes this WAR. If the WAR is a file structure and not an actual WAR
449 * file, all children are delted first, then the directory is removed.
450 *
451 * @throws IOException
452 * if there is an error removing the WAR from the file system.
453 */
454 public void removeWar() throws IOException
455 {
456 if (warStruct.getRootDirectory().exists())
457 {
458 warStruct.remove();
459 }
460 else
461 {
462 throw new FileNotFoundException("PortletApplicationWar ," + warStruct.getRootDirectory()
463 + ", does not exist.");
464 }
465 }
466
467 /***
468 * Validate a PortletApplicationDefinition tree AFTER its
469 * WebApplicationDefinition has been loaded. Currently, only the security
470 * role references of the portlet definitions are validated:
471 * <ul>
472 * <li>A security role reference should reference a security role through a
473 * roleLink. A warning message is logged if a direct reference is used.
474 * <li>For a security role reference a security role must be defined in the
475 * web application. An error message is logged and a
476 * PortletApplicationException is thrown if not.
477 * </ul>
478 *
479 * @throws PortletApplicationException
480 */
481 public void validate() throws PortletApplicationException
482 {
483 if (portletApp == null || webApp == null)
484 {
485 throw new IllegalStateException(
486 "createWebApp() and createPortletApp() must be called before invoking validate()");
487 }
488
489 SecurityRoleSet roles = webApp.getSecurityRoles();
490 Collection portlets = portletApp.getPortletDefinitions();
491 Iterator portletIterator = portlets.iterator();
492 while (portletIterator.hasNext())
493 {
494 PortletDefinition portlet = (PortletDefinition) portletIterator.next();
495 SecurityRoleRefSet securityRoleRefs = portlet.getInitSecurityRoleRefSet();
496 Iterator roleRefsIterator = securityRoleRefs.iterator();
497 while (roleRefsIterator.hasNext())
498 {
499 SecurityRoleRef roleRef = (SecurityRoleRef) roleRefsIterator.next();
500 String roleName = roleRef.getRoleLink();
501 if (roleName == null || roleName.length() == 0)
502 {
503 roleName = roleRef.getRoleName();
504 }
505 if (roles.get(roleName) == null)
506 {
507 String errorMsg = "Undefined security role " + roleName + " referenced from portlet "
508 + portlet.getName();
509 throw new PortletApplicationException(errorMsg);
510 }
511 }
512 }
513 }
514
515 /***
516 *
517 * <p>
518 * processWebXML
519 * </p>
520 *
521 * Infuses this PortletApplicationWar's web.xml file with
522 * <code>servlet</code> and a <code>servlet-mapping</code> element for
523 * the JetspeedContainer servlet. This is only done if the descriptor does
524 * not already contain these items.
525 *
526 * @throws MetaDataException
527 * if there is a problem infusing
528 */
529 public void processWebXML() throws MetaDataException
530 {
531 SAXBuilder builder = new SAXBuilder();
532 Writer webXmlWriter = null;
533 InputStream webXmlIn = null;
534
535 try
536 {
537
538
539 builder.setEntityResolver(new EntityResolver()
540 {
541 public InputSource resolveEntity( java.lang.String publicId, java.lang.String systemId )
542 throws SAXException, java.io.IOException
543 {
544
545 if (systemId.equals("http://java.sun.com/dtd/web-app_2_3.dtd"))
546 {
547 return new InputSource(getClass().getResourceAsStream("web-app_2_3.dtd"));
548 }
549 else return null;
550 }
551 });
552
553 Document doc = null;
554
555 try
556 {
557 webXmlIn = getInputStream(WEB_XML_PATH);
558 doc = builder.build(webXmlIn);
559 }
560 catch (FileNotFoundException fnfe)
561 {
562
563 File file = File.createTempFile("j2-temp-", ".xml");
564 FileWriter writer = new FileWriter(file);
565 writer.write(WEB_XML_STRING);
566 writer.close();
567 doc = builder.build(file);
568 file.delete();
569 }
570
571
572 if (webXmlIn != null)
573 {
574 webXmlIn.close();
575 }
576
577 JetspeedWebApplicationRewriterFactory rewriterFactory = new JetspeedWebApplicationRewriterFactory();
578 JetspeedWebApplicationRewriter rewriter = rewriterFactory.getInstance(doc);
579 rewriter.processWebXML();
580
581 if (rewriter.isChanged())
582 {
583 System.out.println("Writing out infused web.xml for " + paName);
584 XMLOutputter output = new XMLOutputter(Format.getPrettyFormat());
585 webXmlWriter = getWriter(WEB_XML_PATH);
586 output.output(doc, webXmlWriter);
587 webXmlWriter.flush();
588
589 }
590
591 if(rewriter.isPortletTaglibAdded())
592 {
593
594 String path = Jetspeed.getRealPath("WEB-INF/tld");
595 if (path != null)
596 {
597 File portletTaglibDir = new File(path);
598 File child = new File(warStruct.getRootDirectory(), "WEB-INF/tld");
599 DirectoryHelper dh = new DirectoryHelper(child);
600 dh.copyFrom(portletTaglibDir, new FileFilter(){
601
602 public boolean accept(File pathname)
603 {
604 return pathname.getName().indexOf("portlet.tld") != -1;
605 }
606 });
607 dh.close();
608 }
609 }
610
611 }
612 catch (Exception e)
613 {
614 e.printStackTrace();
615 throw new MetaDataException("Unable to process web.xml for infusion " + e.toString(), e);
616 }
617 finally
618 {
619 if (webXmlWriter != null)
620 {
621 try
622 {
623 webXmlWriter.close();
624 }
625 catch (IOException e1)
626 {
627
628 }
629 }
630
631 if (webXmlIn != null)
632 {
633 try
634 {
635 webXmlIn.close();
636 }
637 catch (IOException e1)
638 {
639
640 }
641 }
642 }
643
644 }
645
646
647 /***
648 *
649 * <p>
650 * close
651 * </p>
652 * Closes any resource this PortletApplicationWar may have opened.
653 *
654 * @throws IOException
655 */
656 public void close() throws IOException
657 {
658
659 Iterator resources = openedResources.iterator();
660 while (resources.hasNext())
661 {
662 try
663 {
664 Object res = resources.next();
665 if (res instanceof InputStream)
666 {
667 ((InputStream) res).close();
668 }
669 else if (res instanceof OutputStream)
670 {
671 ((OutputStream) res).close();
672 }
673 }
674 catch (Exception e)
675 {
676
677 }
678 }
679
680 }
681
682 /***
683 *
684 * <p>
685 * createClassloader
686 * </p>
687 *
688 * Use this method to create a classloader based on this wars structure.
689 * I.e. it will create a ClassLoader containing the contents of
690 * WEB-INF/classes and WEB-INF/lib and the ClassLoader will be searched in
691 * that order.
692 *
693 *
694 * @param parent
695 * Parent ClassLoader. Can be <code>null</code>
696 * @return @throws
697 * IOException
698 */
699 public ClassLoader createClassloader( ClassLoader parent ) throws IOException
700 {
701 ArrayList urls = new ArrayList();
702 File webInfClasses = null;
703
704 webInfClasses = new File(warStruct.getRootDirectory(), ("WEB-INF/classes/"));
705 if (webInfClasses.exists())
706 {
707 log.info("Adding " + webInfClasses.toURL() + " to class path.");
708 urls.add(webInfClasses.toURL());
709 }
710
711 File webInfLib = new File(warStruct.getRootDirectory(), "WEB-INF/lib");
712
713 if (webInfLib.exists())
714 {
715 File[] jars = webInfLib.listFiles();
716
717 for (int i = 0; i < jars.length; i++)
718 {
719 File jar = jars[i];
720 log.info("Adding " + jar.toURL() + " to class path.");
721 urls.add(jar.toURL());
722 }
723 }
724
725 return new URLClassLoader((URL[]) urls.toArray(new URL[urls.size()]), parent);
726 }
727
728 /***
729 * @return Returns the paName.
730 */
731 public String getPortletApplicationName()
732 {
733 return paName;
734 }
735
736 /***
737 *
738 * <p>
739 * getDeployedPath
740 * </p>
741 *
742 * @return A string representing the path to this WAR in the form of a URL
743 * or <code>null</code> is the URL could not be created.
744 */
745 public String getDeployedPath()
746 {
747 try
748 {
749 return warStruct.getRootDirectory().toURL().toExternalForm();
750 }
751 catch (MalformedURLException e)
752 {
753 return null;
754 }
755 }
756
757 public FileSystemHelper getFileSystem()
758 {
759 return warStruct;
760 }
761 }