--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry output="crawler/basic/build/bin" kind="src" path="crawler/basic/src"/>
+ <classpathentry output="gps/build/bin" kind="src" path="gps/src"/>
+ <classpathentry output="crawler/kissweb/build/testbin" kind="src" path="crawler/kissweb/test"/>
+ <classpathentry output="crawler/kissweb/WebRoot/WEB-INF/classes" kind="src" path="crawler/kissweb/src"/>
+ <classpathentry output="crawler/basic/build/testbin" kind="src" path="crawler/basic/test"/>
+ <classpathentry output="crawler/kiss/build/bin" kind="src" path="crawler/kiss/src"/>
+ <classpathentry output="support/build/bin" kind="src" path="support/src"/>
+ <classpathentry output="support/build/testbin" kind="src" path="support/test"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/external/jtidy-4aug2000r7-dev.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/dbunit-2.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/jmock-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/jmock-cglib-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/junit-3.8.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/commons-httpclient-3.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/jtidy-4aug2000r7-dev.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/test/jmock-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/test/jmock-cglib-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/dom4j-1.6.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/ehcache-1.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/log4j-1.2.9.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/antlr-2.7.5H3.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/asm.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/asm-attrs.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/cglib-2.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/hibernate-3.0.5.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/jmock-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/jmock-cglib-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/jta.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/oro-2.0.6.jar"/>
+ <classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/commons-codec-1.3.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/jaxen-1.1-beta-4.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/activation.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/mail.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/xerces-2.4.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/commons-email-1.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/emma_ant-2.0.5312.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/emma-2.0.5312.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/resources/test"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/commons-beanutils-1.7.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/commons-logging-1.0.2.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/jaxen-1.1-beta-4.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/commons-collections-3.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/spring-1.2.5.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/activation.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-beanutils-1.7.0.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-codec-1.3.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-email-1.0.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-httpclient-3.0.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-logging-1.0.2.jar"/>
+ <classpathentry sourcepath="/usr/java/dom4j/src/java" kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/dom4j-1.6.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/jaxen-1.1-beta-4.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/jstl-1.1.2.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/jtidy-4aug2000r7-dev.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/log4j-1.2.9.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/mail.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/quartz-1.5.1.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/servletapi-2.4.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/spring-1.2.5.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/standard-1.1.2.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/wamblee-crawler-basic.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/wamblee-crawler-kiss.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/wamblee-support.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/xerces-2.4.0.jar"/>
+ <classpathentry kind="lib" path="gps/lib/external/jcommon-1.0.0.jar"/>
+ <classpathentry sourcepath="/usr/java/jfreechart/source" kind="lib" path="gps/lib/external/jfreechart-1.0.1.jar"/>
+ <classpathentry kind="output" path="crawler/basic/build/bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project-module
+ type="WEB"
+ name="utils"
+ id="myeclipse.1145729924792"
+ context-root="/kisscrawler"
+ j2ee-spec="1.4"
+ archive="utils.war">\r
+ <attributes>\r
+ <attribute name="webrootdir" value="/crawler/kissweb/WebRoot" />\r
+ </attributes>\r
+</project-module>\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>utils</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.WebClasspathBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.J2EEProjectValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.DeploymentDescriptorValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.ibm.etools.validation.validationbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.ast.deploy.core.DeploymentBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.ibm.sse.model.structuredbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.genuitec.eclipse.ast.deploy.core.deploymentnature</nature>
+ <nature>com.genuitec.eclipse.j2eedt.core.webnature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+This is the utilities project of wamblee.org. It contains various utilities
+and useful programs for various purposes. The most important part is the
+support library.
+
--- /dev/null
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
--- /dev/null
+Quickstart: Type 'ant dist-lite' to build everything and download dependencies.
+
+You must use a java 5 compiler, jdk 1.4.* or earlier will not work.
+
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:build/header.xml">
+ <!ENTITY delegator SYSTEM "file:build/delegator.xml">
+ <!ENTITY trailer SYSTEM "file:build/trailer.xml">
+]>
+
+<project name="utils" basedir=".">
+
+ <property name="project.home" value="."/>
+
+ &header;
+
+ <property name="projects" value="support,crawler,socketproxy,gps"/>
+
+ &delegator;
+
+</project>
--- /dev/null
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">\r
+<xsl:output method="html" indent="yes"/>\r
+<xsl:decimal-format decimal-separator="." grouping-separator="," />\r
+\r
+<!-- Checkstyle XML Style Sheet by Stephane Bailliez <sbailliez@apache.org> -->\r
+<!-- Part of the Checkstyle distribution found at http://checkstyle.sourceforge.net -->\r
+<!-- Usage (generates checkstyle_report.html): -->\r
+<!-- <checkstyle failonviolation="false" config="${check.config}"> -->\r
+<!-- <fileset dir="${src.dir}" includes="**/*.java"/> -->\r
+<!-- <formatter type="xml" toFile="${doc.dir}/checkstyle_report.xml"/> -->\r
+<!-- </checkstyle> -->\r
+<!-- <style basedir="${doc.dir}" destdir="${doc.dir}" -->\r
+<!-- includes="checkstyle_report.xml" -->\r
+<!-- style="${doc.dir}/checkstyle-noframes.xsl"/> -->\r
+\r
+<xsl:template match="checkstyle">\r
+ <html>\r
+ <head>\r
+ <style type="text/css">\r
+ .bannercell {\r
+ border: 0px;\r
+ padding: 0px;\r
+ }\r
+ body {\r
+ margin-left: 10;\r
+ margin-right: 10;\r
+ font:normal 80% arial,helvetica,sanserif;\r
+ background-color:#FFFFFF;\r
+ color:#000000;\r
+ }\r
+ .a td { \r
+ background: #efefef;\r
+ }\r
+ .b td { \r
+ background: #fff;\r
+ }\r
+ th, td {\r
+ text-align: left;\r
+ vertical-align: top;\r
+ }\r
+ th {\r
+ font-weight:bold;\r
+ background: #ccc;\r
+ color: black;\r
+ }\r
+ table, th, td {\r
+ font-size:100%;\r
+ border: none\r
+ }\r
+ table.log tr td, tr th {\r
+ \r
+ }\r
+ h2 {\r
+ font-weight:bold;\r
+ font-size:140%;\r
+ margin-bottom: 5;\r
+ }\r
+ h3 {\r
+ font-size:100%;\r
+ font-weight:bold;\r
+ background: #525D76;\r
+ color: white;\r
+ text-decoration: none;\r
+ padding: 5px;\r
+ margin-right: 2px;\r
+ margin-left: 2px;\r
+ margin-bottom: 0;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <a name="top"></a>\r
+ <!-- jakarta logo -->\r
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">\r
+ <tr>\r
+ <td class="bannercell" rowspan="2">\r
+ <!--a href="http://jakarta.apache.org/">\r
+ <img src="http://jakarta.apache.org/images/jakarta-logo.gif" alt="http://jakarta.apache.org" align="left" border="0"/>\r
+ </a-->\r
+ </td>\r
+ <td class="text-align:right"><h2>CheckStyle Audit</h2></td>\r
+ </tr>\r
+ <tr>\r
+ <td class="text-align:right">Designed for use with <a href='http://checkstyle.sourceforge.net/'>CheckStyle</a> and <a href='http://jakarta.apache.org'>Ant</a>.</td>\r
+ </tr>\r
+ </table>\r
+ <hr size="1"/>\r
+ \r
+ <!-- Summary part -->\r
+ <xsl:apply-templates select="." mode="summary"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- Package List part -->\r
+ <xsl:apply-templates select="." mode="filelist"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- For each package create its part -->\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:apply-templates select="."/>\r
+ <p/>\r
+ <p/>\r
+ </xsl:for-each>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ \r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+ \r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="filelist"> \r
+ <h3>Files</h3>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Name</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:variable name="errorCount" select="count(error)"/> \r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><a href="#f-{@name}"><xsl:value-of select="@name"/></a></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table> \r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="file">\r
+ <a name="f-{@name}"></a>\r
+ <h3>File <xsl:value-of select="@name"/></h3>\r
+ \r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Error Description</th>\r
+ <th>Line</th>\r
+ </tr>\r
+ <xsl:for-each select="error">\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="@message"/></td>\r
+ <td><xsl:value-of select="@line"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table>\r
+ <a href="#top">Back to top</a>\r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="summary">\r
+ <h3>Summary</h3>\r
+ <xsl:variable name="fileCount" select="count(file)"/>\r
+ <xsl:variable name="errorCount" select="count(file/error)"/>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Files</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="$fileCount"/></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </table>\r
+ </xsl:template>\r
+ \r
+ <xsl:template name="alternated-row">\r
+ <xsl:attribute name="class">\r
+ <xsl:if test="position() mod 2 = 1">a</xsl:if>\r
+ <xsl:if test="position() mod 2 = 0">b</xsl:if>\r
+ </xsl:attribute> \r
+ </xsl:template> \r
+</xsl:stylesheet>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0"?>\r
+\r
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\r
+\r
+<xsl:template match="/">\r
+ <html>\r
+ <head>\r
+ <title>Sun Coding Style Violations</title>\r
+ </head>\r
+ <body bgcolor="#FFFFEF">\r
+ <p><b>Coding Style Check Results</b></p>\r
+ <table border="1" cellspacing="0" cellpadding="2">\r
+ <tr bgcolor="#CC9966">\r
+ <th colspan="2"><b>Summary</b></th>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total files checked</td>\r
+ <td><xsl:number level="any" value="count(descendant::file)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Files with errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::file[error])"/></td>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::error)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Errors per file</td>\r
+ <td><xsl:number level="any" value="count(descendant::error) div count(descendant::file)"/></td>\r
+ </tr>\r
+ </table>\r
+ <hr align="left" width="95%" size="1"/>\r
+ <p>The following are violations of the Sun Coding-Style Standards:</p>\r
+ <p/>\r
+ <xsl:apply-templates/>\r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+\r
+<xsl:template match="file[error]">\r
+ <table bgcolor="#AFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> File: </th>\r
+ <td>\r
+ <xsl:value-of select="@name"/>\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ <table bgcolor="#DFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> Line Number </th>\r
+ <th> Error Message </th>\r
+ </tr>\r
+ <xsl:apply-templates select="error"/>\r
+ </table>\r
+ <p/>\r
+</xsl:template>\r
+\r
+<xsl:template match="error">\r
+ <tr>\r
+ <td>\r
+ <xsl:value-of select="@line"/>\r
+ </td>\r
+ <td>\r
+ <xsl:value-of select="@message"/>\r
+ </td>\r
+ </tr>\r
+</xsl:template>\r
+\r
+</xsl:stylesheet>\r
--- /dev/null
+
+
+ <!-- =============================================================
+ This build file provides delegation to a number of other
+ projects. The projects must be defined in a projects
+ property which contains a comma-separated list of directories
+ to which we must delegate.
+ This build file should be included from another build file
+ which needs to delegate to other targets.
+ ================================================================= -->
+
+ <!-- ================================================================
+ Delegates the build to a number of other projects.
+ Two properties must be set for this to work:
+ - projects: a comma separated list of directories to execute the
+ build in.
+ - targets: List of targets (separated by commas) to execute.
+ ================================================================ -->
+ <target name="delegator" >
+ <for list="${targets}" param="target">
+ <sequential>
+ <for list="${projects}" param="proj">
+ <sequential>
+ <echo>
+=====================================================================
+Executing target '@{target}' for @{proj}
+=====================================================================
+ </echo>
+ <ant dir="@{proj}"
+ inheritAll="false"
+ target="@{target}"/>
+ </sequential>
+ </for>
+ </sequential>
+ </for>
+ </target>
+
+
+
+ <target name="info">
+ <echo>Delegator build file for delegating a task
+ to several other build files. </echo>
+ </target>
+
+ <target name="init_delegator" depends="init_directory_properties,import_header">
+ </target>
+
+
+ <target name="deps" depends="init_delegator" description="download dependencies">
+ <antcall target="delegator">
+ <param name="targets" value="deps"/>
+ </antcall>
+ </target>
+
+ <target name="clean-deps" depends="init_delegator" description="download dependencies">
+ <antcall target="delegator">
+ <param name="targets" value="clean-deps"/>
+ </antcall>
+ </target>
+
+ <target name="clean" depends="init_delegator" description="Clean all targets">
+ <antcall target="delegator">
+ <param name="targets" value="clean"/>
+ </antcall>
+ </target>
+
+ <target name="compile" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="compile,dist-lite-product"/>
+ </antcall>
+ </target>
+
+ <target name="schemaexport" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="schemaexport"/>
+ </antcall>
+ </target>
+
+ <target name="jar" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="jar"/>
+ </antcall>
+ </target>
+
+ <target name="javadoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="javadoc"/>
+ </antcall>
+ </target>
+
+ <target name="testjavadoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="testjavadoc"/>
+ </antcall>
+ </target>
+
+ <target name="pdfdoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="pdfdoc"/>
+ </antcall>
+ </target>
+
+ <target name="doccheck" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="doccheck"/>
+ </antcall>
+ </target>
+
+ <target name="clean-dist" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="clean-dist"/>
+ </antcall>
+ </target>
+
+ <target name="dist-lite" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite"/>
+ </antcall>
+ </target>
+
+ <target name="dist-javadoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-javadoc"/>
+ </antcall>
+ </target>
+
+ <target name="dist-lite-product" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-product"/>
+ </antcall>
+ </target>
+
+ <target name="dist-lite-test" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-test"/>
+ </antcall>
+ </target>
+
+
+ <target name="dist" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist"/>
+ </antcall>
+ </target>
+
+ <target name="checkstyle" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="checkstyle"/>
+ </antcall>
+ </target>
+
+ <target name="format" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="format"/>
+ </antcall>
+ </target>
+
+ <target name="simian" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="simian"/>
+ </antcall>
+ </target>
+
+ <target name="macker" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="macker"/>
+ </antcall>
+ </target>
+
+ <target name="testclasses" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="test" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-product,dist-lite-test,test"/>
+ </antcall>
+ </target>
+
+ <target name="junit-reports" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="junit-reports,dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="jcov-reports" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="jcov-reports,dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="reports" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="reports,dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="forrest" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="forrest"/>
+ </antcall>
+ </target>
+
+
+ <target name="schemaupdate" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="schemaupdate"/>
+ </antcall>
+ </target>
+
+
--- /dev/null
+<!-- PROPERTIES -->
+
+<target name="echoUserPropertiesFileFound" if="user.properties.found">
+ <echo>Using properties file ${user.property.file}</echo>
+</target>
+
+<target name="echoUserPropertiesFileNotFound" unless="user.properties.found">
+ <echo>Properties file ${user.property.file} not found, reverting to defaults</echo>
+</target>
+
+
+<property name="project.home" value=".."/>
+<property name="build.dir" value="${project.home}/build"/>
+<property name="lib.dir" value="${project.home}/lib"/>
+<property environment="env"/>
+
+<target name="download.dep">
+ <if>
+ <isset property="proxyhost"/>
+ <then>
+ <setproxy proxyhost="${proxyhost}" proxyport="${proxyport}"/>
+ </then>
+ </if>
+ <if>
+ <isset property="artifact"/>
+ <then>
+ <echo>Getting dependence ${group}/${artifact}/${version}</echo>
+ <dependencies verbose="true" fileSetId="my.deps">
+ <dependency group="${group}" artifact="${artifact}" version="${version}"/>
+ </dependencies>
+ <copy todir="${download.dir}" flatten="yes">
+ <fileset refid="my.deps"/>
+ </copy>
+ </then>
+ <else>
+ <echo>Getting dependence ${group}/${version}</echo>
+ <dependencies verbose="true" fileSetId="my.deps">
+ <dependency group="${group}" version="${version}"/>
+ </dependencies>
+ <copy todir="${download.dir}" flatten="yes">
+ <fileset refid="my.deps"/>
+ </copy>
+ </else>
+ </if>
+</target>
+
+<!-- LOG4j -->
+<target name="log4j.d">
+ <antcall target="download.dep">
+ <param name="group" value="log4j"/>
+ <param name="version" value="1.2.9"/>
+ </antcall>
+</target>
+
+<target name="commons-beanutils.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-beanutils"/>
+ <param name="version" value="1.7.0"/>
+ </antcall>
+</target>
+
+<target name="commons-collections.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-collections"/>
+ <param name="version" value="3.1"/>
+ </antcall>
+</target>
+
+
+<target name="commons-logging.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-logging"/>
+ <param name="artifact" value="commons-logging"/>
+ <param name="version" value="1.0.2"/>
+ </antcall>
+</target>
+
+<target name="commons-codec.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-codec"/>
+ <param name="version" value="1.3"/>
+ </antcall>
+</target>
+
+<target name="commons-email.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-email"/>
+ <param name="version" value="1.0"/>
+ </antcall>
+</target>
+
+<target name="servletapi.d">
+ <antcall target="download.dep">
+ <param name="group" value="servletapi"/>
+ <param name="version" value="2.4"/>
+ </antcall>
+</target>
+
+<target name="jstl.d">
+ <antcall target="download.dep">
+ <param name="group" value="jstl"/>
+ <param name="version" value="1.1.2"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="taglibs"/>
+ <param name="artifact" value="standard"/>
+ <param name="version" value="1.1.2"/>
+ </antcall>
+</target>
+
+
+
+<target name="quartz.d">
+ <antcall target="download.dep">
+ <param name="group" value="quartz"/>
+ <param name="version" value="1.5.1"/>
+ </antcall>
+</target>
+
+
+
+<target name="logging.d" depends="log4j.d,commons-logging.d">
+</target>
+
+<target name="dom4j.d">
+ <antcall target="download.dep">
+ <param name="group" value="dom4j"/>
+ <param name="version" value="1.6"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="jaxen"/>
+ <param name="version" value="1.1-beta-4"/>
+ </antcall>
+</target>
+
+<target name="jfreechart.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/jfreechart-1.0.1">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="ehcache.d">
+ <antcall target="download.dep">
+ <param name="group" value="ehcache"/>
+ <param name="version" value="1.1"/>
+ </antcall>
+</target>
+
+<target name="xerces.d">
+ <antcall target="download.dep">
+ <param name="group" value="xerces"/>
+ <param name="version" value="2.4.0"/>
+ </antcall>
+</target>
+
+<target name="oro.d">
+ <antcall target="download.dep">
+ <param name="group" value="oro"/>
+ <param name="version" value="2.0.6"/>
+ </antcall>
+</target>
+
+<target name="cglib.d">
+ <antcall target="download.dep">
+ <param name="group" value="cglib"/>
+ <param name="version" value="2.1"/>
+ </antcall>
+</target>
+
+<target name="hibernate.d" depends="cglib.d,oro.d">
+ <antcall target="download.dep">
+ <param name="group" value="hibernate"/>
+ <param name="version" value="3.0.5"/>
+ </antcall>
+</target>
+
+<target name="hibernate.standalone.d" depends="hibernate.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/hibernate">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="spring.d">
+ <antcall target="download.dep">
+ <param name="group" value="springframework"/>
+ <param name="artifact" value="spring"/>
+ <param name="version" value="1.2.5"/>
+ </antcall>
+</target>
+
+<target name="activation.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/jaf-1.0.2">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="mail.d" depends="activation.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/javamail-1.3.3_01">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+
+
+<target name="httpclient.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-httpclient"/>
+ <param name="version" value="3.0"/>
+ </antcall>
+</target>
+
+<target name="jtidy.d">
+ <antcall target="download.dep">
+ <param name="group" value="jtidy"/>
+ <param name="version" value="4aug2000r7-dev"/>
+ </antcall>
+</target>
+
+<property name="support.dist.dir" value="${lib.dir}/wamblee/support"/>
+<target name="wamblee.support.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${support.dist.dir}">
+ <include name="wamblee-support.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.support.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${support.dist.dir}">
+ <include name="wamblee-support-test.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+
+<target name="junit.d">
+ <antcall target="download.dep">
+ <param name="group" value="junit"/>
+ <param name="version" value="3.8.1"/>
+ </antcall>
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/test">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="emma.d">
+ <antcall target="download.dep">
+ <param name="group" value="emma"/>
+ <param name="artifact" value="emma"/>
+ <param name="version" value="2.0.5312"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="emma"/>
+ <param name="artifact" value="emma_ant"/>
+ <param name="version" value="2.0.5312"/>
+ </antcall>
+</target>
+
+<target name="jmock.d">
+ <antcall target="download.dep">
+ <param name="group" value="jmock"/>
+ <param name="artifact" value="jmock"/>
+ <param name="version" value="1.0.1"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="jmock"/>
+ <param name="artifact" value="jmock-cglib"/>
+ <param name="version" value="1.0.1"/>
+ </antcall>
+</target>
+
+<target name="dbunit.d">
+ <antcall target="download.dep">
+ <param name="group" value="dbunit"/>
+ <param name="version" value="2.1"/>
+ </antcall>
+</target>
+
+
+<target name="antlr.d">
+ <antcall target="download.dep">
+ <param name="group" value="antlr"/>
+ <param name="version" value="2.7.2"/>
+ </antcall>
+</target>
+
+<target name="checkstyle.d" depends="antlr.d,commons-beanutils.d,commons-logging.d,commons-collections.d">
+ <antcall target="download.dep">
+ <param name="group" value="checkstyle"/>
+ <param name="version" value="4.1"/>
+ </antcall>
+</target>
+
+<!-- common test dependencies for all test code -->
+<target name="test.d" depends="junit.d,jmock.d,dbunit.d,emma.d">
+</target>
+
+<!-- downloaded dependencies for ant tasks -->
+<target name="ant.d" depends="checkstyle.d">
+</target>
+
+
+
+ <target name="import_header" unless="build_header_included">
+
+ <property name="ant.lib.dir" value="${build.dir}/lib/ant"/>
+ <property name="test.lib.dir" value="lib/test"/>
+ <property name="special.lib.dir" value="${build.dir}/lib/special"/>
+ <property name="ant.downloaded.lib.dir" value="${build.dir}/lib/ant/downloaded"/>
+
+
+ <!-- ========================================================================================
+ Ant task defs
+ ======================================================================================== -->
+
+ <!-- ant-contrib integration -->
+ <property name="ant.contrib.home" value="${ant.lib.dir}/ant-contrib-1.0b2"/>
+ <taskdef resource="net/sf/antcontrib/antlib.xml">
+ <classpath>
+ <pathelement location="${ant.contrib.home}/ant-contrib.jar" />
+ </classpath>
+ </taskdef>
+
+ <!-- taskdef for ant-dependencies task -->
+ <taskdef name="dependencies" classpath="${ant.lib.dir}/ant-dependencies.jar"
+ classname="org.apache.tools.ant.taskdefs.optional.dependencies.Dependencies"/>
+
+ <!-- Emma integration -->
+ <if>
+ <available file="${test.lib.dir}"/>
+ <then>
+ <path id="emma.lib">
+ <fileset dir="${test.lib.dir}" includes="emma*.jar"/>
+ </path>
+ <taskdef resource="emma_ant.properties" classpathref="emma.lib" />
+ <property name="emma.enabled" value="false"/>
+ </then>
+ </if>
+
+ <!-- checkstyle -->
+ <property name="checkstyle.home" value="${ant.downloaded.lib.dir}"/>
+ <taskdef resource="checkstyletask.properties">
+ <classpath>
+ <fileset dir="${checkstyle.home}">
+ <include name="*.jar"/>
+ </fileset>
+ </classpath>
+ </taskdef>
+ <property name="checkstyle.rules" value="style.xml"/>
+ <property name="checkstyle.test.rules" value="test-style.xml"/>
+
+
+ <!-- jalopy -->
+ <!-- TMP download this dependence as well
+ <property name="jalopy.home" value="${lib.dir}/jalopy-ant-0.6.2"/>
+ <property name="jalopy.rules" value="${build.dir}/formatting-rules.xml"/>
+ <taskdef name="jalopy"
+ classname="de.hunsicker.jalopy.plugin.ant.AntPlugin">
+ <classpath>
+ <fileset dir="${jalopy.home}">
+ <include name="*.jar" />
+ </fileset>
+ </classpath>
+ </taskdef>
+ -->
+
+ <!-- simian integration -->
+ <!-- TMP download this dependence as well
+ <property name="simian.home" value="${lib.dir}/simian"/>
+ <taskdef resource="simiantask.properties" classpath="${simian.home}/simian.jar"/>
+ -->
+
+ <!-- macker integration -->
+ <!-- TMP download this dependence as well
+ <property name="macker.home" value="${lib.dir}/macker-0.4.2"/>
+ <path id="macker.path">
+ <fileset dir="${macker.home}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+ <taskdef name="macker"
+ classname="net.innig.macker.ant.MackerAntTask"
+ classpathref="macker.path"/>
+
+ -->
+
+ <!-- ========================================================================================
+ Locate user properties to (optionally) override defaults
+ ======================================================================================== -->
+
+ <!--
+ Give user a chance to override without editing this file
+ (and without typing -D each time they run it).
+
+ The following properties must be defined in this file:
+ - jcoverage.home: root of the jcoverage installation.
+ - jalopy.home: root of the jalopy installation.
+ -->
+ <property name="user.property.file" value="${user.home}/wamblee.properties"/>
+ <available file="${user.property.file}" property="user.properties.found"/>
+ <property file="${user.property.file}"/>
+
+ <antcall target="echoUserPropertiesFileFound"/>
+ <antcall target="echoUserPropertiesFileNotFound"/>
+
+ <!-- project properties defaults, can be overriden in the user's properties file -->
+
+ <!-- The project.home property can also be overriden in a build.xml
+ in case the source is in a subdirectory and not necessarily
+ in the top-level directory -->
+
+
+ <if>
+ <isset property="webroot.dir"/>
+ <then>
+ <property name="external.lib.dir" value="${webroot.dir}/WEB-INF/lib"/>
+ </then>
+ <else>
+ <property name="external.lib.dir" value="lib/external"/>
+ </else>
+ </if>
+
+ <property name="build.properties.dir" value="${project.home}" />
+ <property name="build.properties.name" value="build.properties" />
+
+ <mkdir dir="${external.lib.dir}"/>
+ <mkdir dir="${test.lib.dir}"/>
+
+ <property name="conf.dir" value="conf"/>
+ <property name="log4j.properties.dir" value="${conf.dir}/properties"/>
+
+ <!-- Compilation properties -->
+ <property name="javac.debug" value="true"/>
+ <property name="javac.debug.level" value="lines,vars,source"/>
+ <property name="javac.source" value="1.5"/>
+
+ <!-- ========================================================================================
+ Standard path definitions to be used with all projects
+ ======================================================================================= -->
+ <property name="xmlschemas.dir" value="${conf.dir}/schemas"/>
+ <path id="xmlschemas.path">
+ <pathelement location="${xmlschemas.dir}"/>
+ </path>
+
+ <!-- ========================================================================================
+ Path definitions for external libraries.
+ ======================================================================================== -->
+
+ <!-- Ignore system classpath! -->
+ <property name="build.sysclasspath" value="ignore" />
+
+ <!-- JUnit -->
+ <property name="junit.halt.on.failure" value="false"/>
+ <property name="unitreport" value="cl-unit.xml"/>
+
+ <!-- DocCheck path -->
+ <property name="doccheck.home" value="${ant.lib.dir}/doccheck1.2b2/doccheck.jar"/>
+ <path id="doccheck.path">
+ <pathelement location="${doccheck.home}"/>
+ </path>
+
+ <!-- PdfDoclet path -->
+ <property name="pdfdoclet.home" value="${ant.lib.dir}/pdfdoclet-1.0.2-all.jar"/>
+ <path id="pdfdoclet.path">
+ <pathelement location="${pdfdoclet.home}"/>
+ </path>
+
+ <!-- Hibernate paths -->
+ <!-- name of the file in the source directory containing
+ the names of hibernate files to look at in the correct order -->
+ <property name="hibernate.home" value="${lib.dir}/hibernate-3.0"/>
+ <path id="hibernate.basic.path">
+ <fileset dir="${hibernate.home}">
+ <include name="*.jar"/>
+ </fileset>
+ <path refid="xerces.path"/>
+ <path refid="dom4j.path"/>
+ </path>
+ <path id="hibernate.appserver.path">
+ <path refid="hibernate.basic.path"/>
+ <fileset dir="${hibernate.home}/appserver">
+ <include name="*.jar"/>
+ </fileset>
+ </path>
+ <path id="hibernate.standalone.path">
+ <path refid="hibernate.basic.path"/>
+ <fileset dir="${hibernate.home}/standalone">
+ <include name="*.jar"/>
+ </fileset>
+ </path>
+
+ <!-- Commented the inclusion of a system-wide path in the user's environment into a
+ classpath. System wide classpaths should not be used. -->
+ <!-- property name="classpath_id" value="build.path" / -->
+ <!-- property name="classpath" refid="${classpath_id}"/ -->
+ <property name="classpath" value=""/>
+
+
+ <!-- ========================================================================================
+ Database settings
+ ======================================================================================== -->
+ <property name="database" value="Derby"/>
+
+
+
+ <!-- ========================================================================================
+ Information
+ ======================================================================================== -->
+ <echo level="debug" message="project.home=${project.home}"/>
+ <echo level="debug" message="project.libs=${project.libs}"/>
+ <echo level="debug" message="classpath=${classpath}"/>
+
+
+ <!-- ========================================================================================
+ Included marker
+ ======================================================================================== -->
+ <property name="build_header_included" value="true" />
+</target>
+
+<!-- =================================================================
+ Sets up the directory definitions for the module
+ ================================================================= -->
+<target name="init_directory_properties" depends="import_header">
+ <!-- Set module properties for this build -->
+
+ <property name="module.home" value="." />
+
+ <!-- Replace the dash by a / to create a relative directory
+ from a module name -->
+ <propertyregex property="module.reldir"
+ input="${module.name}"
+ regexp="-"
+ replace="/"
+ global="true"
+ defaultValue="${module.name}"/>
+ <!-- property name="module.build.dir"
+ value="${build.dir}/${module.reldir}" / -->
+ <property name="module.build.dir" value="build"/>
+ <property name="module.api.forrest.dir" value="${forrest.build.site.dir}/api/${module.name}" />
+ <if>
+ <isset property="webroot.dir"/>
+ <then>
+ <property name="module.classes.dir" value="${webroot.dir}/WEB-INF/classes" />
+ </then>
+ <else>
+ <property name="module.classes.dir" value="${module.build.dir}/bin" />
+ </else>
+ </if>
+
+ <property name="module.testclasses.dir" value="${module.build.dir}/testbin" />
+ <!-- Directory where generated SQL will be put by the schema export-->
+ <property name="module.sql.dir" value="${module.build.dir}/sql" />
+
+
+ <property name="module.jcovclasses.dir"
+ value="${module.build.dir}/testjcov" />
+ <property name="module.jars.dir" value="${module.build.dir}/jars" />
+ <property name="module.jar.file"
+ value="${module.jars.dir}/${module.jar.name}" />
+ <property name="module.source.dir" value="${module.home}/src" />
+ <property name="module.forrest.src.dir" value="${module.home}/xdocs" />
+ <property name="module.ejbsource.dir" value="${module.home}/ejbsrc" />
+ <property name="module.test.dir" value="${module.home}/test" />
+ <property name="module.testjar.name"
+ value="${module.name}-test.jar" />
+ <property name="module.testresourcesjar.name"
+ value="${module.name}-test-resources.jar" />
+ <property name="module.resource.dir"
+ value="${module.home}/resources" />
+ <property name="module.testresource.dir"
+ value="${module.resource.dir}/test" />
+ <property name="module.testoutput.dir"
+ value="${module.resource.dir}/testoutput"/>
+ <property name="module.report.dir"
+ value="${module.build.dir}/testresults" />
+ <property name="module.emmareport.dir"
+ value="${module.report.dir}/html/emma"/>
+ <property name="module.docbase.dir"
+ value="${module.build.dir}/docs" />
+ <property name="module.javadoc.dir"
+ value="${module.docbase.dir}/api" />
+ <property name="module.javadocjar.name"
+ value="${module.name}-doc.jar" />
+ <property name="module.testjavadocjar.name"
+ value="${module.name}-test-doc.jar" />
+ <property name="module.testjavadoc.dir"
+ value="${module.docbase.dir}/testapi" />
+ <property name="module.pdfdoc.dir"
+ value="${module.docbase.dir}/pdf" />
+ <property name="module.doccheck.dir"
+ value="${module.docbase.dir}/doccheck" />
+ <property name="module.checkstyle.dir"
+ value="${module.docbase.dir}/checkstyle" />
+ <property name="module.jar.name" value="${module.name}.jar" />
+
+ <!-- Replace the dash by a / to create a relative directory
+ from a module name -->
+ <property name="module.dist.dir" value="${lib.dir}/${module.reldir}" />
+</target>
+
+<!-- ============================================================================
+ Initialize the environment for other tasks. Normally, every task which
+ does not depend on any other task should at least depend on init.
+ ============================================================================ -->
+
+ <target name="help" depends="import_header">
+ <antcall target="help-within-module"/>
+ <antcall target="help-outside-module"/>
+ </target>
+
+ <target name="help-within-module" depends="init_directory_properties" if="module.name">
+ <antcall target="help-impl"/>
+ </target>
+
+ <target name="help-outside-module" unless="module.name">
+ <antcall target="help-outside-module-2">
+ <param name="module.name" value="MODULE_NAME"/>
+ </antcall>
+ </target>
+
+ <target name="help-outside-module-2" depends="init_directory_properties">
+ <antcall target="help-impl"/>
+ </target>
+
+
+ <target name="help-impl">
+ <echo>
+
+ Preparation:
+
+ To execute with a specific proxy host and port, start ant with the
+ command-line options -Dproxyhost=hostname -Dproxyport=portnumber.
+
+ deps: Download dependencies, this is necessary for using
+ any of the build targets.
+ clean-deps: Remove downloaded dependencies.
+
+ General build targets:
+
+ clean: Cleans up all build results (excluding the dist location).
+ compile: Compiles all java clasess. Target dir ${module.classes.dir}
+ jar: Creates a jar file. Target: ${module.jar.file}
+ dist: Makes the build results available for other modules Target: ${module.dist.dir}.
+ dist-lite: Similar to dist but only creates the jars for code and test code. Useful
+ during development because it takes much less time than the dist
+ target
+ clean-dist: Cleans up the dist directories.
+
+ Test:
+
+ test: Compile and run tests.
+ junit-reports: As test but generates a test report. Target: ${module.report.dir}(/html/unit)
+ reports: As junit-reports but also generates an emma test coverage
+ report
+
+ Note: By specifying -Dtest=TestCaseName on the ant command line,
+ only the specified testcase will be run.
+
+ Emma can also be executed manually:
+
+ emma: Overwrites the production classes by their instrumented versions.
+ emma-reports: Generates emma code coverage reports after running the
+ testcases with instrumented classes.
+
+ Javadoc targets:
+
+ javadoc: Generates javadoc. Target: ${module.javadoc.dir}
+ doccheck: Checks documentation. Target: ${module.doccheck.dir}
+
+ Forrest:
+
+ forrest: Generates project docs using forrest.
+
+ Code analysis:
+
+ checkstyle: Checks the style of the code.
+ format: Formats to the code in accordance with the checkstyle rules.
+ simian: Analyse similarities in the code.
+
+ Building a web application:
+
+ To build a web application archive (WAR) you must set the property
+ webroot.dir to the value of the directory which is the root of your
+ web application. This must be done before the header.xl is
+ sourced. The build support will then make sure that
+ classes and libs are put in the correct directories below
+ ${webroot.dir}/WEB-INF, and it will build a web archive.
+
+ Database targets: ** database targets are not functional yet **
+
+ For all targets below the database is configured using
+ hibernate.properties (hibernate.dialect property) and
+ the JDBC connection properties in database.properties.
+
+ The schemaexport and schemaupdate targets require the
+ setting of a property named hibernate.filelist. The value
+ of the property must be the fully qualified class name of
+ a concrete subclass of ConfigFileList which has a default
+ public constructor and defines the Hibernate mapping files that
+ can be used.
+
+ schemaexport: Generate SQL code to create the required database structures.
+
+ startdb: Startup a lightweight database. The database
+ type to start is obtained from the currently
+ configured database.
+
+ schemaupdate: Populate the database with a schema by running
+ Hibernate schemaupdate against the currently
+ configured database.
+ </echo>
+ </target>
+
+
--- /dev/null
+Ant-Contrib library
+
+This library is for contributed Ant tasks that have
+not been approved for inclusion into the ant core or
+optional library.
+
+Requirements
+-------------------------
+Runtime:
+ Requires APACHE Ant Version 1.5 or above. Note, that output
+ handlers on the ForEach task will not properly report the
+ task which is outputting the message unless you are using
+ Ant version 1.5.2 or greater.
+
+ In addition, the <for> task will not work on versions prior
+ to Ant 1.6
+
+Compilation:
+ Requires Ant Version 1.6 or greater to compile and build the
+ package.
+
+
+Inclusion in your project
+-------------------------
+ The easiest way to use the tasks is to use
+
+<taskdef resource="net/sf/antcontrib/antlib.xml">
+ <classpath>
+ <pathelement location="your/path/to/ant-contrib-${version}.jar" />
+ </classpath>
+</taskdef>
+
+in your build file. If the jar file is on your CLASSPATH or in
+ANT_HOME/lib you can even simplify this to read
+
+<taskdef resource="net/sf/antcontrib/antlib.xml" />
+
+For projects which will run under 1.5 versions, you would
+use the .properties file instead of the antlib.xml file:
+
+<taskdef resource="net/sf/antcontrib/antcontrib.properties">
+ <classpath>
+ <pathelement location="your/path/to/ant-contrib.jar" />
+ </classpath>
+</taskdef>
+
--- /dev/null
+ Sun Microsystems, Inc.
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED
+SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY
+"AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE
+MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA
+PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT.
+IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY,
+INDICATE YOUR ACCEPTANCE OF THESE TERMS BY
+SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS
+AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE
+TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR
+PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE
+IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE"
+BUTTON AT THE END OF THIS AGREEMENT.
+
+1. LICENSE TO USE. Sun grants you a
+non-exclusive and non-transferable license for the
+internal use only of the accompanying software and
+documentation and any error corrections provided
+by Sun (collectively "Software"), by the number of
+users and the class of computer hardware for which
+the corresponding fee has been paid.
+
+2. RESTRICTIONS Software is confidential and
+copyrighted. Title to Software and all associated
+intellectual property rights is retained by Sun
+and/or its licensors. Except as specifically
+authorized in any Supplemental License Terms, you
+may not make copies of Software, other than a
+single copy of Software for archival purposes.
+Unless enforcement is prohibited by applicable
+law, you may not modify, decompile, or reverse
+engineer Software. Software is not designed or
+licensed for use in on-line control of aircraft,
+air traffic, aircraft navigation or aircraft
+communications; or in the design, construction,
+operation or maintenance of any nuclear facility.
+No right, title or interest in or to any
+trademark, service mark, logo or trade name of Sun
+or its licensors is granted under this Agreement.
+
+3. LIMITED WARRANTY. Sun warrants to you that for
+a period of ninety (90) days from the date of
+purchase, as evidenced by a copy of the receipt,
+the media on which Software is furnished (if any)
+will be free of defects in materials and
+workmanship under normal use. Except for the
+foregoing, Software is provided "AS IS". Your
+exclusive remedy and Sun's entire liability under
+this limited warranty will be at Sun's option to
+replace Software media or refund the fee paid for
+Software.
+
+4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN
+THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS,
+REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
+IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE
+DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE
+DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
+
+5. LIMITATION OF LIABILITY. TO THE EXTENT NOT
+PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS
+LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL,
+INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT
+OF OR RELATED TO THE USE OF OR INABILITY TO USE
+SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES. In no event will
+Sun's liability to you, whether in contract, tort
+(including negligence), or otherwise, exceed the
+amount paid by you for Software under this
+Agreement. The foregoing limitations will apply
+even if the above stated warranty fails of its
+essential purpose.
+
+6. Termination. This Agreement is effective
+until terminated. You may terminate this
+Agreement at any time by destroying all copies of
+Software. This Agreement will terminate
+immediately without notice from Sun if you fail to
+comply with any provision of this Agreement. Upon
+Termination, you must destroy all copies of
+Software.
+
+7. Export Regulations. All Software and
+technical data delivered under this Agreement are
+subject to US export control laws and may be
+subject to export or import regulations in other
+countries. You agree to comply strictly with all
+such laws and regulations and acknowledge that you
+have the responsibility to obtain such licenses to
+export, re-export, or import as may be required
+after delivery to you.
+
+8. U.S. Government Restricted Rights. If
+Software is being acquired by or on behalf of the
+U.S. Government or by a U.S. Government prime
+contractor or subcontractor (at any tier), then
+the Government's rights in Software and
+accompanying documentation will be only as set
+forth in this Agreement; this is in accordance
+with 48 CFR 227.7201 through 227.7202-4 (for
+Department of Defense (DOD) acquisitions) and with
+48 CFR 2.101 and 12.212 (for non-DOD
+acquisitions).
+
+9. Governing Law. Any action related to this
+Agreement will be governed by California law and
+controlling U.S. federal law. No choice of law
+rules of any jurisdiction will apply.
+
+10. Severability. If any provision of this
+Agreement is held to be unenforceable, this
+Agreement will remain in effect with the provision
+omitted, unless omission would frustrate the
+intent of the parties, in which case this
+Agreement will immediately terminate.
+
+11. Integration. This Agreement is the entire
+agreement between you and Sun relating to its
+subject matter. It supersedes all prior or
+contemporaneous oral or written communications,
+proposals, representations and warranties and
+prevails over any conflicting or additional terms
+of any quote, order, acknowledgment, or other
+communication between the parties relating to its
+subject matter during the term of this Agreement.
+No modification of this Agreement will be binding,
+unless in writing and signed by an authorized
+representative of each party.
+
+For inquiries please contact: Sun Microsystems,
+Inc. 901 San Antonio Road, Palo Alto, California
+94303
+
+
+SUPPLEMENTAL LICENSE TERMS
+JDBCTM 2.0 INTERFACE CLASSES
+
+These supplemental license terms ("Supplement")
+add to or modify the terms of the Binary Code
+License Agreement (collectively, the
+"Agreement"). Capitalized terms not defined in
+this Supplement shall have the same meanings
+ascribed to them in the Agreement. These
+Supplement terms shall supersede any inconsistent
+or conflicting terms in the Agreement, or in any
+license contained within the Software.
+
+1. License to Distribute. Sun grants you a
+non-exclusive, non-transferable, limited license
+to reproduce and distribute the binary and/or
+source code form of the Software to third party
+end users through multiple tiers of distribution,
+provided that you: (i) distribute the Software
+complete and unmodified in its original Java
+Archive file, and only bundled as a part of your
+program that incorporates the Software
+("Program"); (ii) do not distribute additional
+software intended to replace any component(s) of
+the Software; (iii) agree to incorporate the most
+current version of the Software that was available
+from Sun no later than 180 days prior to each
+production release of the Program; (iv) do not
+remove or alter any proprietary legends or notices
+contained in or on the Software; (v) only
+distribute the Program pursuant to a license
+agreement that protects Sun's interest consistent
+with the terms contained in the Agreement; (vi)
+may not create, or authorize your licensees to cr!
+eate additional classes, interfaces, or
+subpackages that are contained in the "java"
+"javax" or "sun" packages or similar as specified
+by Sun in any class file naming convention; and
+(vii) agree to defend and indemnify Sun and its
+licensors from and against any damages, costs,
+liabilities, settlement amounts and/or expenses
+(including attorneys' fees) incurred in connection
+with any claim, lawsuit or action by any third
+party that arises or results from the use or
+distribution of any and all Programs.
+
+2. Trademarks and Logos. You acknowledge as
+between you and Sun that Sun owns the Java
+trademark and all Java-related trademarks, logos
+and icons including the Coffee Cup and Duke ("Java
+Marks") and agree to comply with the Java
+Trademark Guidelines at
+http://java.sun.com/trademarks.html.
+
+
+
+
+
+
\ No newline at end of file
--- /dev/null
+
+Sun Microsystems, Inc.
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE" BUTTON AT THE END OF THIS AGREEMENT.
+
+1. LICENSE TO USE. Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively "Software"), by the number of users and the class of computer hardware for which the corresponding fee has been paid.
+
+2. RESTRICTIONS. Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes. Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software. Licensee acknowledges that Licensed Software is not designed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun Microsystems, Inc. disclaims any express or implied warranty of fitness for such uses. No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement.
+
+3. LIMITED WARRANTY. Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided "AS IS". Your exclusive remedy and Sun's entire liability under this limited warranty will be at Sun's option to replace Software media or refund the fee paid for Software.
+
+4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
+
+5. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Sun's liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose.
+
+6. Termination. This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement. Upon Termination, you must destroy all copies of Software.
+
+7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you.
+
+8. U.S. Government Restricted Rights. If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Government's rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions).
+
+9. Governing Law. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply.
+
+10. Severability. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate.
+
+11. Integration. This Agreement is the entire agreement between you and Sun relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party.
+
+JAVATM INTERFACE CLASSES
+JAVA TRANSACTION API (JTA), VERSION 1.0.1B, MAINTENANCE RELEASE
+SUPPLEMENTAL LICENSE TERMS
+
+These supplemental license terms ("Supplemental Terms") add to or modify the terms of the Binary Code License Agreement (collectively, the "Agreement"). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software.
+
+1. Software Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement, including, but not limited to Section 3 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce internally and use internally the binary form of the Software, complete and unmodified, for the sole purpose of designing, developing and testing your Java applets and applications ("Programs").
+
+2. License to Distribute Software. In addition to the license granted in Section 1 (Software Internal Use and Development License Grant) of these Supplemental Terms, subject to the terms and conditions of this Agreement, including but not limited to Section 3 (Java Technology Restrictions), Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute the Software in binary form only, provided that you (i) distribute the Software complete and unmodified and only bundled as part of your Programs, (ii) do not distribute additional software intended to replace any component(s) of the Software, (iii) do not remove or alter any proprietary legends or notices contained in the Software, (iv) only distribute the Software subject to a license agreement that protects Sun's interests consistent with the terms contained in this Agreement, and (v) agree to defend and indemnify Sun and its licensors from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys' fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software.
+
+3. Java Technology Restrictions. You may not modify the Java Platform Interface ("JPI", identified as classes contained within the "java" package or any subpackages of the "java" package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI. In the event that you create an additional class and associated API(s) which (i) extends the functionality of the Java Platform, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar convention as specified by Sun in any naming convention designation.
+
+4. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET trademarks and all SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET-related trademarks, service marks, logos and other brand designations ("Sun Marks"), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http://www.sun.com/policies/trademarks. Any use you make of the Sun Marks inures to Sun's benefit.
+
+5. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement. Source code may not be redistributed unless expressly provided for in this Agreement.
+
+6. Termination for Infringement. Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right.
+
+For inquiries please contact: Sun Microsystems, Inc. 4150 Network Circle, Santa Clara, California 95054.
+
+
--- /dev/null
+<?xml version="1.0"?>\r
+<!DOCTYPE module PUBLIC\r
+ "-//Puppy Crawl//DTD Check Configuration 1.2//EN"\r
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">\r
+\r
+<!--\r
+\r
+ Checkstyle configuration that checks the sun coding conventions from:\r
+\r
+ - the Java Language Specification at\r
+ http://java.sun.com/docs/books/jls/second_edition/html/index.html\r
+\r
+ - the Sun Code Conventions at http://java.sun.com/docs/codeconv/\r
+\r
+ - the Javadoc guidelines at\r
+ http://java.sun.com/j2se/javadoc/writingdoccomments/index.html\r
+\r
+ - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html\r
+\r
+ - some best practices\r
+\r
+ Checkstyle is very configurable. Be sure to read the documentation at\r
+ http://checkstyle.sf.net (or in your downloaded distribution).\r
+\r
+ Most Checks are configurable, be sure to consult the documentation.\r
+\r
+ To completely disable a check, just comment it out or delete it from the file.\r
+\r
+ Finally, it is worth reading the documentation.\r
+\r
+-->\r
+\r
+<module name="Checker">\r
+\r
+ <!-- Checks that a package.html file exists for each package. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->\r
+ <!-- module name="PackageHtml"/ -->\r
+\r
+ <!-- Checks whether files end with a new line. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->\r
+ <module name="NewlineAtEndOfFile"/>\r
+\r
+ <!-- Checks that property files contain the same keys. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->\r
+ <module name="Translation"/>\r
+\r
+\r
+ <module name="TreeWalker">\r
+\r
+ <!-- Checks for Javadoc comments. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html -->\r
+ <!-- module name="JavadocMethod"/ -->\r
+ <!-- module name="JavadocType"/ -->\r
+ <!-- module name="JavadocVariable"/ -->\r
+ <!-- module name="JavadocStyle"/ -->\r
+ \r
+ <!-- Checks for Naming Conventions. -->\r
+ <!-- See http://checkstyle.sf.net/config_naming.html -->\r
+ <module name="ConstantName"/>\r
+ <module name="LocalFinalVariableName"/>\r
+ <module name="LocalVariableName"/>\r
+ <module name="MemberName">
+ <property name="format" value="_[a-z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="MethodName" />\r
+ <module name="PackageName"/>\r
+ <module name="ParameterName">
+ <property name="format" value="^a[A-Z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="StaticVariableName">
+ <property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
+ </module>\r
+ <module name="TypeName"/>\r
+\r
+\r
+ <!-- Checks for Headers -->\r
+ <!-- See http://checkstyle.sf.net/config_header.html -->\r
+ <!-- <module name="Header"> -->\r
+ <!-- The follow property value demonstrates the ability -->\r
+ <!-- to have access to ANT properties. In this case it uses -->\r
+ <!-- the ${basedir} property to allow Checkstyle to be run -->\r
+ <!-- from any directory within a project. See property -->\r
+ <!-- expansion, -->\r
+ <!-- http://checkstyle.sf.net/config.html#properties -->\r
+ <!-- <property -->\r
+ <!-- name="headerFile" -->\r
+ <!-- value="${basedir}/java.header"/> -->\r
+ <!-- </module> -->\r
+\r
+ <!-- Following interprets the header file as regular expressions. -->\r
+ <!-- <module name="RegexpHeader"/> -->\r
+\r
+\r
+ <!-- Checks for imports -->\r
+ <!-- See http://checkstyle.sf.net/config_import.html -->\r
+ <module name="AvoidStarImport"/>\r
+ <module name="IllegalImport"/> <!-- defaults to sun.* packages -->\r
+ <module name="RedundantImport"/>\r
+ <module name="UnusedImports"/>\r
+\r
+\r
+ <!-- Checks for Size Violations. -->\r
+ <!-- See http://checkstyle.sf.net/config_sizes.html -->\r
+ <module name="FileLength"/>\r
+ <module name="LineLength">
+ <property name="max" value="120"/>
+ </module>\r
+ <module name="MethodLength"/>\r
+ <module name="ParameterNumber"/>\r
+\r
+\r
+ <!-- Checks for whitespace -->\r
+ <!-- See http://checkstyle.sf.net/config_whitespace.html -->\r
+ <module name="EmptyForIteratorPad"/>\r
+ <!-- module name="MethodParamPad"/ -->\r
+ <!-- module name="NoWhitespaceAfter"/ -->\r
+ <module name="NoWhitespaceBefore"/>\r
+ <module name="OperatorWrap"/>\r
+ <module name="ParenPad"/>\r
+ <!-- module name="TypecastParenPad"/ -->\r
+ <module name="TabCharacter"/>\r
+ <module name="WhitespaceAfter"/>\r
+ <!-- module name="WhitespaceAround">
+ <property name="tokens" value=" ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR,MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,GENERIC_START,GENERIC_END,TYPE_EXTENSION_AND,WILDCARD_TYPE"/>
+ </module -->\r
+\r
+\r
+ <!-- Modifier Checks -->\r
+ <!-- See http://checkstyle.sf.net/config_modifiers.html -->\r
+ <module name="ModifierOrder"/>\r
+ <module name="RedundantModifier"/>\r
+\r
+\r
+ <!-- Checks for blocks. You know, those {}'s -->\r
+ <!-- See http://checkstyle.sf.net/config_blocks.html -->\r
+ <module name="AvoidNestedBlocks">
+ <property name="allowInSwitchCase" value="true"/>
+ </module>\r
+ <module name="EmptyBlock"/>\r
+ <module name="LeftCurly"/>\r
+ <module name="NeedBraces"/>\r
+ <module name="RightCurly"/>\r
+\r
+\r
+ <!-- Checks for common coding problems -->\r
+ <!-- See http://checkstyle.sf.net/config_coding.html -->\r
+ <module name="AvoidInlineConditionals"/>\r
+ <!-- module name="DoubleCheckedLocking"/ --> \r
+ <module name="EmptyStatement"/>\r
+ <module name="EqualsHashCode"/>\r
+ <module name="HiddenField"/>\r
+ <module name="IllegalInstantiation"/>\r
+ <module name="InnerAssignment"/>\r
+ <module name="MagicNumber">
+ <property name="ignoreNumbers" value="-1,0,1,2,3,4"/>
+ </module>\r
+ <module name="MissingSwitchDefault"/>\r
+ <module name="RedundantThrows"/>\r
+ <module name="SimplifyBooleanExpression"/>\r
+ <module name="SimplifyBooleanReturn"/>\r
+ <!-- module name="ExplicitInitialization"/ -->\r
+\r
+ <!-- Checks for class design -->\r
+ <!-- See http://checkstyle.sf.net/config_design.html -->\r
+ <!-- module name="DesignForExtension"/ -->\r
+ <module name="FinalClass"/>\r
+ <module name="HideUtilityClassConstructor"/>\r
+ <module name="InterfaceIsType"/>\r
+ <module name="VisibilityModifier"/>\r
+\r
+\r
+ <!-- Miscellaneous other checks. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html -->\r
+ <module name="ArrayTypeStyle"/>\r
+ <!-- module name="FinalParameters"/ -->\r
+ <!-- module name="GenericIllegalRegexp">\r
+ <property name="format" value="\s+$"/>\r
+ <property name="message" value="Line has trailing spaces."/>\r
+ </module -->\r
+ <module name="TodoComment"/>\r
+ <module name="UpperEll"/>\r
+\r
+ </module>\r
+\r
+</module>\r
--- /dev/null
+<!-- PROPERTIES -->
+
+
+<!-- Default entry point for the build -->
+<target name="all" depends="clean, init, compile, jar" />
+
+<target name="startup" depends="init_directory_properties">
+ <property name="module.classpath_id" value="module.build.path" />
+ <property name="module.classpath" refid="${module.classpath_id}" />
+
+ <path id="module.build.path">
+ <fileset dir="${external.lib.dir}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+
+ <path id="module.testbuild.path">
+ <fileset dir="${test.lib.dir}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+
+</target>
+
+<target name="init" depends="import_header,startup">
+ <tstamp />
+</target>
+
+<!-- ============================================================================
+ Cleanup
+ ============================================================================ -->
+
+<target name="ant.deps">
+</target>
+
+<target name="deps" depends="import_header">
+ <antcall target="module.build.deps">
+ <param name="download.dir" value="${external.lib.dir}"/>
+ </antcall>
+ <antcall target="test.d">
+ <param name="download.dir" value="${test.lib.dir}"/>
+ </antcall>
+ <antcall target="ant.d">
+ <param name="download.dir" value="${ant.downloaded.lib.dir}"/>
+ </antcall>
+ <antcall target="module.test.deps">
+ <param name="download.dir" value="${test.lib.dir}"/>
+ </antcall>
+</target>
+
+<target name="clean" depends="init_directory_properties,base-test-clean">
+ <delete dir="${module.classes.dir}" />
+ <delete dir="${module.testclasses.dir}" />
+ <delete dir="${module.testoutput.dir}" />
+ <delete dir="${module.jars.dir}" />
+ <delete dir="${module.docbase.dir}" />
+ <delete dir="${module.report.dir}" />
+ <delete dir="${module.sql.dir}" />
+ <delete dir="${module.build.dir}"/>
+</target>
+
+<target name="clean-deps" depends="init_directory_properties">
+ <delete>
+ <fileset dir="${external.lib.dir}" includes="*"/>
+ </delete>
+ <delete>
+ <fileset dir="${test.lib.dir}" includes="*"/>
+ </delete>
+ <delete>
+ <fileset dir="${ant.downloaded.lib.dir}" includes="*"/>
+ </delete>
+</target>
+
+<!-- ============================================================================
+ Compilation of java code.
+ ============================================================================ -->
+
+<!-- Compilation of java code, requires a srcdirs variable to be
+ set to a path of one of more directories separated by : or ;
+-->
+<target name="compile-impl" depends="init">
+ <mkdir dir="${module.classes.dir}" />
+ <echo level="info" message="compile ${module.name}, source dirs ${srcdirs}" />
+ <javac srcdir="${srcdirs}" source="${javac.source}"
+ destdir="${module.classes.dir}" classpath="${module.classpath}"
+ debug="${javac.debug}" debuglevel="${javac.debug.level}" />
+ <copy todir="${module.classes.dir}">
+ <fileset dir="${module.source.dir}" excludes="**/*.java" />
+ </copy>
+</target>
+
+<!-- Compilation of regular source code -->
+<target name="compile-src" unless="ejbsource.available" depends="init">
+ <antcall target="compile-impl">
+ <param name="srcdirs" value="${module.source.dir}"/>
+ </antcall>
+</target>
+
+<!-- Compilation of regular source code and generated source code
+ for EJBs -->
+<target name="compile-src-ejb" if="ejbsource.available" depends="init">
+ <antcall target="compile-impl">
+ <param name="srcdirs" value="${module.source.dir}:${module.ejbsource.dir}"/>
+ </antcall>
+</target>
+
+<target name="compile" depends="init">
+ <available file="${module.ejbsource.dir}" type="dir" property="ejbsource.available"/>
+ <antcall target="compile-src"/>
+ <antcall target="compile-src-ejb"/>
+</target>
+
+<!-- ============================================================================
+ Generating a jar file.
+ ============================================================================ -->
+
+<target name="jar" depends="compile">
+ <mkdir dir="${module.jars.dir}" />
+ <echo level="info" message="jar cdmvbase ${module.name}" />
+ <!-- Hack: how to remove the schemas directory from the jar when it does not exist -->
+ <mkdir dir="${xmlschemas.dir}" />
+ <jar jarfile="${module.jars.dir}/${module.jar.name}"
+ compress="${jarcompress}">
+ <fileset dir="${module.classes.dir}" />
+ <fileset dir="${xmlschemas.dir}" />
+ </jar>
+</target>
+
+
+<!-- ============================================================================
+ Generate javadoc.
+ ============================================================================ -->
+
+<target name="javadoc" depends="init">
+ <!-- create javadocs -->
+ <javadoc packagenames="*" destdir="${module.javadoc.dir}"
+ author="true" version="true" use="true" private="yes"
+ windowtitle="Dragon documentation" source="${javac.source}"
+ sourcepath="${module.source.dir}"
+ classpath="${module.classpath}">
+ <arg value="-docfilessubdirs"/>
+ <arg value="-overview"/>
+ <arg value="${module.source.dir}/overview.html"/>
+ </javadoc>
+</target>
+
+<target name="testjavadoc" depends="testclasses">
+ <!-- create javadocs -->
+ <javadoc packagenames="*" destdir="${module.testjavadoc.dir}"
+ author="true" version="true" use="true" private="yes"
+ windowtitle="Dragon test documentation" source="${javac.source}"
+ sourcepath="${module.test.dir}"
+ classpathref="test.classpath">
+ </javadoc>
+</target>
+
+<!-- ============================================================================
+ Generate javadoc in pdf format
+ ============================================================================ -->
+
+<target name="pdfdoc" depends="init">
+ <!-- create javadocs -->
+ <mkdir dir="${module.pdfdoc.dir}" />
+ <javadoc packagenames="*" author="true" version="true" private="yes"
+ source="${javac.source}" doclet="com.tarsec.javadoc.pdfdoclet.PDFDoclet"
+ docletpathref="pdfdoclet.path"
+ classpath="${module.classpath}"
+ sourcepath="${module.source.dir}"
+ additionalparam="-pdf ${module.pdfdoc.dir}/${module.name}.pdf">
+ <!-- fileset dir="${module.source.dir}">
+ <include name="**/*.java" />
+ </fileset -->
+ </javadoc>
+ <echo
+ message="Unfortuantely, this target will have generated some errors but the pdf will still look fine" />
+ <echo>Result is available in ${module.pdfdoc.dir}"</echo>
+</target>
+
+<!-- ============================================================================
+ Check javadoc
+ ============================================================================ -->
+
+<target name="doccheck" depends="init">
+ <mkdir dir="${module.doccheck.dir}" />
+ <javadoc packagenames="*" destdir="${module.doccheck.dir}"
+ author="true" version="true"
+ doclet="com.sun.tools.doclets.doccheck.DocCheck"
+ docletpathref="doccheck.path"
+ classpath="${module.classpath}"
+ sourcepath="${module.source.dir}">
+ </javadoc>
+ <echo>Results are available in ${module.doccheck.dir}"</echo>
+</target>
+
+<!-- ============================================================================
+ Copy build results to a standard location.
+ ============================================================================ -->
+
+<target name="clean-dist" depends="clean">
+ <delete>
+ <fileset dir="${module.dist.dir}" includes="**"/>
+ </delete>
+</target>
+
+<target name="dist-lite-product" depends="deps,jar">
+ <mkdir dir="${module.dist.dir}"/>
+ <delete>
+ <fileset dir="${module.dist.dir}" excludes="**/CVS" />
+ </delete>
+ <copy todir="${module.dist.dir}" flatten="yes">
+ <fileset dir="${module.build.dir}">
+ <include name="**/*.jar" />
+ </fileset>
+ <fileset dir="${module.build.dir}">
+ <include name="**/*.pdf" />
+ </fileset>
+ </copy>
+ <if>
+ <isset property="webroot.dir"/>
+ <then>
+ <jar destfile="${module.dist.dir}/${module.name}.war"
+ basedir="${webroot.dir}"/>
+ </then>
+ </if>
+</target>
+
+<target name="dist-lite-test" depends="deps,testclasses">
+ <jar destfile="${module.dist.dir}/${module.testjar.name}"
+ basedir="${module.testclasses.dir}"
+ includes="**/*.class"/>
+ <jar destfile="${module.dist.dir}/${module.testresourcesjar.name}"
+ basedir="${module.testresource.dir}" />
+</target>
+
+<target name="dist-lite" depends="clean,dist-lite-product,dist-lite-test">
+ <if>
+ <isset property="post-dist-lite"/>
+ <then>
+ <antcall target="post-dist-lite"/>
+ </then>
+ </if>
+</target>
+
+<target name="dist-javadoc" depends="javadoc,pdfdoc,testjavadoc">
+ <jar destfile="${module.dist.dir}/${module.javadocjar.name}"
+ basedir="${module.javadoc.dir}" />
+ <jar destfile="${module.dist.dir}/${module.testjavadocjar.name}"
+ basedir="${module.testjavadoc.dir}" />
+ <copy todir="${module.api.forrest.dir}">
+ <fileset dir="${module.javadoc.dir}" />
+ </copy>
+</target>
+
+<target name="dist"
+ depends="dist-lite,dist-javadoc"
+ description="copying compiled sources to dist location and copy documentation to forrest site">
+ <echo
+ message="Copying build results for ${module.name} to a location where other modules can find it." />
+</target>
+
+<!-- ============================================================================
+ Check style,
+ ============================================================================ -->
+
+<target name="checkstyle-impl">
+ <checkstyle config="${build.dir}/${checkstyle.rules}"
+ failOnViolation="false">
+ <classpath>
+ <pathelement path="${checkstyle.classpath}"/>
+ </classpath>
+ <fileset dir="${checkstyle.srcdir}" includes="**/*.java"/>
+ <formatter type="xml" tofile="${module.checkstyle.dir}/${checkstyle.report}.xml"/>
+ </checkstyle>
+ <style style="${build.dir}/checkstyle-simple.xsl"
+ in="${module.checkstyle.dir}/${checkstyle.report}.xml"
+ out="${module.checkstyle.dir}/${checkstyle.report}.html"/>
+ <echo>Results are available at ${module.checkstyle.dir}/${checkstyle.report}.html</echo>
+</target>
+
+<target name="checkstyle" depends="testclasses">
+ <mkdir dir="${module.checkstyle.dir}"/>
+ <antcall target="checkstyle-impl">
+ <param name="checkstyle.classpath" refid="module.build.path"/>
+ <param name="checkstyle.srcdir" value="${module.source.dir}"/>
+ <param name="checkstyle.report" value="source-results"/>
+ </antcall>
+ <antcall target="checkstyle-impl">
+ <param name="checkstyle.classpath" refid="module.testbuild.path"/>
+ <param name="checkstyle.srcdir" value="${module.test.dir}"/>
+ <param name="checkstyle.report" value="test-results"/>
+ </antcall>
+</target>
+
+<!-- ============================================================================
+ Formatting source code.
+ ============================================================================ -->
+<target name="format" depends="init">
+ <jalopy loglevel="INFO" classpathref="module.testbuild.path"
+ convention="${jalopy.rules}">
+ <fileset dir="${module.source.dir}">
+ <include name="**/*.java" />
+ </fileset>
+ <fileset dir="${module.test.dir}">
+ <include name="**/*.java" />
+ </fileset>
+ </jalopy>
+</target>
+
+<!-- =========================================================================
+ Check redundancy in the code
+ ========================================================================= -->
+<target name="simian" depends="init">
+ <simian threshold="7">
+ <fileset dir="${module.source.dir}" includes="**/*.java" />
+ </simian>
+</target>
+
+
+<!-- =========================================================================
+ Check dependencies using macker.
+ ========================================================================= -->
+<property name="macker.file" value="macker.xml" />
+<target name="macker" depends="compile">
+ <available file="${macker.file}" property="macker.file.found" />
+ <fail message="Macker source file ${macker.file} was not found."
+ unless="macker.file.found" />
+ <macker>
+ <rules dir="." includes="macker.xml" />
+ <classes dir="${module.classes.dir}">
+ <include name="**/*.class" />
+ </classes>
+ <classpath>
+ <pathelement path="${module.classes.dir}" />
+ <path refid="module.build.path" />
+ </classpath>
+ </macker>
+</target>
+
+
+<!-- ===========================================================================
+ JUnit tests.
+ =========================================================================== -->
+
+<target name="testclasses" depends="compile">
+ <mkdir dir="${module.test.dir}" />
+ <mkdir dir="${module.testresource.dir}" />
+ <mkdir dir="${module.testclasses.dir}" />
+ <path id="test.classpath">
+ <pathelement path="${module.classes.dir}" />
+ <pathelement path="${module.test.dir}" />
+ <path refid="module.testbuild.path" />
+ <path refid="module.build.path" />
+ </path>
+ <javac srcdir="${module.test.dir}"
+ destdir="${module.testclasses.dir}"
+ debug="${javac.debug}" debuglevel="${javac.debug.level}" compiler="modern"
+ source="${javac.source}" failonerror="yes">
+ <classpath>
+ <path refid="test.classpath"/>
+ </classpath>
+ </javac>
+ <copy todir="${module.testclasses.dir}">
+ <fileset dir="${module.test.dir}" excludes="**/*.java" />
+ <fileset dir="${module.testresource.dir}" />
+ </copy>
+</target>
+
+<!-- test-base is a target mean to be used by other targets.
+ It expects one parameter names 'testedclasses' to indicate the directory
+ where the compiled test classes reside.
+-->
+
+<target name="base-test-clean">
+ <delete file="coverage.ec"/>
+ <delete file="coverage.em"/>
+</target>
+
+
+<target name="base-test-impl" depends="testclasses">
+ <mkdir dir="${module.report.dir}" />
+ <antcall target="emma"/>
+ <junit haltonfailure="${junit.halt.on.failure}" printsummary="on" dir="." fork="yes" showoutput="yes">
+ <batchtest todir="${module.report.dir}">
+ <fileset dir="${module.testclasses.dir}">
+ <include name="${testclassnames}" />
+ <!-- exclude inner classes -->
+ <exclude name="**/*$$*.class"/>
+ </fileset>
+ </batchtest>
+ <formatter type="xml" />
+ <classpath>
+ <path refid="xmlschemas.path" />
+ <pathelement path="${module.testclasses.dir}" />
+ <pathelement path="${testedclasses}" />
+ <pathelement path="${testlibrary}" />
+ <pathelement path="${module.classes.dir}" />
+ <pathelement path="${log4j.properties.dir}" /><!-- for log4j.properties -->
+ <!-- the test resource directory is added to the path as well to make it
+ easy to access resources from tests -->
+ <pathelement path="${module.testresource.dir}" />
+ <path refid="module.testbuild.path" />
+ <path refid="module.build.path" />
+ </classpath>
+ </junit>
+</target>
+
+<target name="base-test-allclasses" unless="test">
+ <echo>Testing all classes</echo>
+ <antcall target="base-test-impl">
+ <param name="testclassnames" value="**/*Test.class" />
+ </antcall>
+</target>
+
+<target name="base-test-singleclass" if="test">
+ <echo>Testing testcases that conform to the pattern *${test}*</echo>
+ <antcall target="base-test-impl">
+ <param name="testclassnames" value="**/*${test}*.class" />
+ </antcall>
+</target>
+
+<target name="base-test">
+ <antcall target="base-test-allclasses" />
+ <antcall target="base-test-singleclass" />
+</target>
+
+<!-- Run junit test -->
+
+<target name="test">
+ <antcall target="base-test">
+ <param name="testedclasses" value="${module.classes.dir}" />
+ </antcall>
+</target>
+
+<!-- reporting target used by other targets. This should not depend on
+ the test target to avoid unnecessary duplicate runs of the tests -->
+
+<target name="base-reports" depends="init">
+ <mkdir dir="${module.report.dir}/html" />
+ <mkdir dir="${module.report.dir}/html/unit" />
+ <junitreport todir="${module.report.dir}" tofile="${unitreport}">
+ <fileset dir="${module.report.dir}">
+ <include name="TEST-*.xml" />
+ </fileset>
+ <report todir="${module.report.dir}/html/unit" />
+ </junitreport>
+</target>
+
+<!-- Run junit tests with HTML reporting -->
+
+<target name="junit-reports" depends="test">
+ <antcall target="base-reports" />
+ <antcall target="emma-reports" />
+</target>
+
+<target name="reports">
+ <antcall target="junit-reports">
+ <param name="emma.enabled" value="true"/>
+ </antcall>
+</target>
+
+ <!-- Instrument classes using emma -->
+
+ <target name="emma" depends="init_directory_properties">
+ <emma enabled="${emma.enabled}">
+ <instr instrpath="${module.classes.dir}" mode="overwrite"/>
+ </emma>
+ </target>
+
+ <target name="emma-reports" depends="init_directory_properties" if="emma.enabled">
+ <mkdir dir="${module.emmareport.dir}"/>
+ <emma enabled="${emma.enabled}" >
+ <report sourcepath="${module.source.dir}">
+ <!-- collect all EMMA data dumps (metadata and runtime): -->
+ <infileset dir="." includes="*.em, *.ec" />
+
+ <txt outfile="${module.emmareport.dir}/report.txt"/>
+ <html outfile="${module.emmareport.dir}/index.html"/>
+ <!-- sourcepath>
+ <dirset dir="${src}" >
+ <include name="." />
+ </dirset>
+ </sourcepath -->
+ </report>
+ </emma>
+ </target>
+
+<!-- =======================================================================
+ FORREST
+ ======================================================================-->
+ <target name="forrest" description="runs Forrest">
+ <property name="forrest.home" location="${env.FORREST_HOME}" />
+ <property name="forrest.ant.home" location="${forrest.home}/tools/ant" />
+ <java classname="org.apache.tools.ant.Main" fork="true" failonerror="true" maxmemory="128M">
+ <classpath>
+ <fileset dir="${forrest.ant.home}/lib">
+ <include name="*.jar" />
+ </fileset>
+ <pathelement path="${java.home}/../lib/tools.jar" />
+ </classpath>
+ <sysproperty key="ant.home" value="${forrest.ant.home}" />
+ <sysproperty key="forrest.home" value="${forrest.home}" />
+ <sysproperty key="basedir" value="${basedir}" />
+ <sysproperty key="java.endorsed.dirs" value="${forrest.home}/lib/endorsed" />
+ <arg line="-f ${forrest.home}/main/forrest.build.xml" />
+ </java>
+ </target>
+
+<!-- ============================================================================
+ Database targets
+ ============================================================================ -->
+
+<!-- Runs a main program found in the test source tree.
+ Properties:
+ - my.test.program: class name of the program to run
+ - my.args: arguments to be passed to the program -->
+<target name="runTestProgram" depends="testclasses">
+ <java classname="${my.test.program}">
+ <arg line="${my.args}"/>
+ <classpath>
+ <path refid="xmlschemas.path" />
+ <path refid="junit.path" />
+ <pathelement path="${module.testclasses.dir}" />
+ <pathelement path="${testedclasses}" />
+ <pathelement path="${testlibrary}" />
+ <pathelement path="${module.classes.dir}" />
+ <pathelement path="${log4j.properties.dir}" /><!-- for log4j.properties -->
+ <!-- the test resource directory is added to the path as well to make it
+ easy to access resources from tests -->
+ <pathelement path="${module.testresource.dir}" />
+ <path refid="module.testbuild.path" />
+ <path refid="module.build.path" />
+ </classpath>
+ </java>
+</target>
+
+<property name="database.starter"
+ value="org.wamblee.persistence.test.DatabaseStarter"/>
+<target name="startdb">
+ <echo>After the database has started, you must populate the database in some way.
+ For instance, using the schemaupdate ant target</echo>
+ <antcall target="runTestProgram">
+ <param name="my.test.program" value="${database.starter}"/>
+ <param name="my.args" value=""/>
+ </antcall>
+</target>
+
+<property name="schemaupdater"
+ value="org.wamblee.test.HibernateUtils"/>
+<target name="schemaupdate">
+ <antcall target="runTestProgram">
+ <param name="my.test.program" value="${schemaupdater}"/>
+ <param name="my.args" value="${hibernate.filelist}"/>
+ </antcall>
+</target>
+
+<target name="schemaexport" depends="init">
+ <mkdir dir="${module.sql.dir}" />
+ <echo>Generating schema in output file ${module.sql.dir}/hibernate.sql</echo>
+ <antcall target="runTestProgram">
+ <param name="my.test.program" value="${schemaupdater}"/>
+ <param name="my.args" value="-export ${module.sql.dir}/hibernate.sql ${hibernate.filelist}"/>
+ </antcall>
+</target>
+
+<!-- INCLUDED MARKER -->
+<property name="build_trailer_included" value="true" />
+
+
--- /dev/null
+This directory contains a generic web crawler (basic directory) and several useful implementations build on top of this.
+
--- /dev/null
+This is a general library for implementing a web crawler.
+
+The crawler works by retrieving an HTML page and transforming the HTML
+(content + presentation) into content using XSLT stylesheets. Using a convention
+for links in the converted content, it becomes possible to build a generic interface on the retrieved pages for navigating through the content.
+
+A configuration file determines how a certain page must be retrieved and transformed.
+
+
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../../build/trailer.xml">
+ <!ENTITY deps SYSTEM "file:deps.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value="../.."/>
+ <property name="module.name" value="wamblee-crawler-basic" />
+
+ &header;
+ &deps;
+
+ <target name="module.build.deps"
+ depends="crawler.src.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="crawler.test.d">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+
+<target name="crawler.src.d"
+ depends="logging.d,dom4j.d,xerces.d,httpclient.d,jtidy.d,wamblee.support.d">
+</target>
+
+<target name="crawler.test.d" depends="wamblee.support.test.d">
+</target>
+
+
+<property name="crawler.dist.dir" value="${lib.dir}/wamblee/crawler/basic"/>
+<target name="wamblee.crawler.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${crawler.dist.dir}">
+ <include name="wamblee-crawler-basic.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.crawler.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${crawler.dist.dir}">
+ <include name="wamblee-crawler-basic-test.jar"/>
+ </fileset>
+ </copy>
+</target>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-basic</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org Basic crawler framework</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>support</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jtidy</groupId>
+ <artifactId>jtidy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xerces</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=DEBUG
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.w3c.dom.Document;
+import org.w3c.tidy.Tidy;
+import org.wamblee.xml.DomUtils;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * General support claas for all kinds of requests.
+ */
+public abstract class AbstractPageRequest implements PageRequest {
+
+ private static final Log LOG = LogFactory.getLog(AbstractPageRequest.class);
+
+ private static final String REDIRECT_HEADER = "Location";
+
+ private int _maxTries;
+
+ private int _maxDelay;
+
+ private NameValuePair[] _params;
+
+ private NameValuePair[] _headers;
+
+ private String _xslt;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the request.
+ *
+ * @param aMaxTries
+ * Maximum retries to perform.
+ * @param aMaxDelay
+ * Maximum delay before executing a request.
+ * @param aParams
+ * Request parameters to use.
+ * @param aHeaders
+ * Request headers to use.
+ * @param aXslt
+ * XSLT used to convert the response.
+ */
+ protected AbstractPageRequest(int aMaxTries, int aMaxDelay,
+ NameValuePair[] aParams, NameValuePair[] aHeaders, String aXslt, XslTransformer aTransformer) {
+ if (aParams == null) {
+ throw new IllegalArgumentException("aParams is null");
+ }
+ if (aHeaders == null) {
+ throw new IllegalArgumentException("aHeaders is null");
+ }
+ if (aXslt == null) {
+ throw new IllegalArgumentException("aXslt is null");
+ }
+ _maxTries = aMaxTries;
+ _maxDelay = aMaxDelay;
+ _params = aParams;
+ _headers = aHeaders;
+ _xslt = aXslt;
+ _transformer = aTransformer;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#overrideXslt(java.lang.String)
+ */
+ public void overrideXslt(String aXslt) {
+ _xslt = aXslt;
+ }
+
+ /**
+ * Gets the parameters for the request.
+ *
+ * @param aParams Additional parameters to use, obtained from another page, most likely as
+ * hidden form fields.
+ * @return Request parameters.
+ */
+ protected NameValuePair[] getParameters(NameValuePair[] aParams) {
+ List<NameValuePair> params = new ArrayList<NameValuePair>();
+ params.addAll(Arrays.asList(_params));
+ params.addAll(Arrays.asList(aParams));
+ return params.toArray(new NameValuePair[0]);
+ }
+
+ /**
+ * Gets the headers for the request.
+ * @return Request headers.
+ */
+ protected NameValuePair[] getHeaders() {
+ return _headers;
+ }
+
+ /**
+ * Executes the request with a random delay and with a maximum number of
+ * retries.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method representing the request.
+ * @return XML document describing the response.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case transformation of the HTML to XML fails.
+ */
+ protected Document executeMethod(HttpClient aClient, HttpMethod aMethod)
+ throws IOException, TransformerException {
+
+ for (NameValuePair header: getHeaders()) {
+ aMethod.setRequestHeader(header.getName(), header.getValue());
+ }
+
+ int triesLeft = _maxTries;
+ while (triesLeft > 0) {
+ triesLeft--;
+ try {
+ return executeMethodWithoutRetries(aClient, aMethod);
+ } catch (TransformerException e) {
+ if (triesLeft == 0) {
+ throw e;
+ }
+ }
+ }
+ throw new RuntimeException("Code should never reach this point");
+ }
+
+ /**
+ * Executes the request without doing any retries in case XSLT
+ * transformation fails.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method to execute.
+ * @return XML document containing the result.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case transformation of the result to XML fails.
+ */
+ protected Document executeMethodWithoutRetries(HttpClient aClient,
+ HttpMethod aMethod) throws IOException, TransformerException {
+ try {
+ aMethod = executeWithRedirects(aClient, aMethod);
+ byte[] xhtmlData = getXhtml(aMethod);
+
+
+ Document transformed = _transformer.transform(xhtmlData,
+ _transformer.resolve(_xslt));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Transformer transformer = TransformerFactory.newInstance()
+ .newTransformer();
+ transformer.setParameter(OutputKeys.INDENT, "yes");
+ transformer.setParameter(OutputKeys.METHOD, "xml");
+ transformer.transform(new DOMSource(transformed), new StreamResult(
+ os));
+ LOG.debug("Transformed result is \n" + os.toString());
+ return transformed;
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException("Transformer configuration problem", e);
+ } finally {
+ // Release the connection.
+ aMethod.releaseConnection();
+ }
+ }
+
+ /**
+ * Gets the result of the HTTP method as an XHTML document.
+ *
+ * @param aMethod
+ * Method to invoke.
+ * @return XHTML as a byte array.
+ * @throws IOException
+ * In case of problems obtaining the XHTML.
+ */
+ private byte[] getXhtml(HttpMethod aMethod) throws IOException {
+ // Transform the HTML into wellformed XML.
+ Tidy tidy = new Tidy();
+ tidy.setXHTML(true);
+ tidy.setQuiet(true);
+ tidy.setShowWarnings(false);
+
+ // We write the jtidy output to XML since the DOM tree it produces is
+ // not namespace aware and namespace awareness is required by XSLT.
+ // An alternative is to configure namespace awareness of the XML parser
+ // in a system wide way.
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Document w3cDoc = tidy.parseDOM(aMethod.getResponseBodyAsStream(), os);
+ DomUtils.removeDuplicateAttributes(w3cDoc);
+ LOG.debug("Content of response is \n" + os.toString());
+
+ ByteArrayOutputStream xhtml = new ByteArrayOutputStream();
+ XMLSerializer serializer = new XMLSerializer(xhtml, new OutputFormat());
+ serializer.serialize(w3cDoc);
+ xhtml.flush();
+
+ return xhtml.toByteArray();
+ }
+
+ /**
+ * Sleeps for a random time but no more than the maximum delay.
+ *
+ */
+ private void delay() {
+ try {
+ Thread.sleep((long) ((float) _maxDelay * Math.random()));
+ } catch (InterruptedException e) {
+ return; // to satisfy checkstyle
+ }
+ }
+
+ /**
+ * Executes the request and follows redirects if needed.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method to use.
+ * @return Final HTTP method used (differs from the parameter passed in in
+ * case of redirection).
+ * @throws IOException
+ * In case of network problems.
+ */
+ private HttpMethod executeWithRedirects(HttpClient aClient,
+ HttpMethod aMethod) throws IOException {
+ delay();
+ int statusCode = aClient.executeMethod(aMethod);
+
+ switch (statusCode) {
+ case HttpStatus.SC_OK: {
+ return aMethod;
+ }
+ case HttpStatus.SC_MOVED_PERMANENTLY:
+ case HttpStatus.SC_MOVED_TEMPORARILY:
+ case HttpStatus.SC_SEE_OTHER: {
+ aMethod.releaseConnection();
+ Header header = aMethod.getResponseHeader(REDIRECT_HEADER);
+ aMethod = new GetMethod(header.getValue());
+ return executeWithRedirects(aClient, aMethod); // TODO protect
+ // against infinite
+ // recursion.
+ }
+ default: {
+ throw new IOException("Method failed: "
+ + aMethod.getStatusLine());
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.dom4j.Element;
+
+/**
+ * An action defined on a page.
+ */
+public interface Action {
+
+ /**
+ * The name of the action.
+ * @return Action name.
+ */
+ String getName();
+
+ /**
+ * Executes the action.
+ * @return New page as a result of the action.
+ * @throws PageException In case of an error obtaining the page.
+ */
+ Page execute() throws PageException;
+
+ /**
+ * Gets a description of the action. THe element returned is the action element
+ * itself.
+ * @return Content as XML.
+ */
+ Element getContent();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Configuration which determines how a specific page must be retrieved and
+ * what transformation should be applied to it.
+ */
+public interface Configuration {
+
+ /**
+ * Gets the page request based on the URL.
+ * @param aUrl Url of the page to retrieve.
+ * @return Page request.
+ */
+ PageRequest getRequest(String aUrl);
+
+ /**
+ * Gets the page request based on the type of the page instead
+ * of on the URL.
+ * @param aType Type of page.
+ * @return Page request.
+ */
+ PageRequest getRequest(PageType aType);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.apache.commons.httpclient.NameValuePair;
+
+
+/**
+ * The object that actually obtains pages based on URL.
+ */
+public interface Crawler {
+
+ /**
+ * Gets the content for a specific page.
+ * @param aUrl Url of page.
+ * @param aParameters Paremeters to supply.
+ * @return Page to retrieve.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Page getPage(String aUrl, NameValuePair[] aParameters) throws PageException;
+
+ /**
+ * Gets the content for a specific page.
+ * @param aUrl Url of page.
+ * @param aParameters Parameters to supply.
+ * @param aType Type of page.
+ * @return Page.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Page getPage(String aUrl, NameValuePair[] aParameters, PageType aType) throws PageException;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.w3c.dom.Document;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Gets a page by issueing a get request.
+ */
+public class GetPageRequest extends AbstractPageRequest {
+
+ /**
+ * Constructs the request.
+ * @param aMaxTries Maximum number of retries.
+ * @param aMaxDelay Maximum delay before executing the request.
+ * @param aParams Request parameters to use.
+ * @param aHeaders Request headers to use.
+ * @param aXslt XSLT to use.
+ */
+ public GetPageRequest(int aMaxTries, int aMaxDelay, NameValuePair[] aParams,
+ NameValuePair[] aHeaders, String aXslt, XslTransformer aTransformer) {
+ super(aMaxTries, aMaxDelay, aParams, aHeaders, aXslt, aTransformer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#getPage(org.apache.commons.httpclient.HttpClient)
+ */
+ public Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient)
+ throws PageException {
+ HttpMethod method = new GetMethod(aUrl);
+ NameValuePair[] params = getParameters(aParams);
+ if (params.length > 0) {
+ String oldQueryString = method.getQueryString();
+ method.setQueryString(params);
+ String queryString = method.getQueryString();
+ if (oldQueryString.length() > 0) {
+ queryString = queryString + '&' + oldQueryString;
+ method.setQueryString(queryString);
+ }
+ }
+ try {
+ return executeMethod(aClient, method);
+ } catch (TransformerException e) {
+ throw new PageException("Transformation problem for url " + aUrl, e);
+ } catch (IOException e) {
+ throw new PageException("Problem getting " + aUrl, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.dom4j.Element;
+
+/**
+ * Represents a retrieved page.
+ */
+public interface Page {
+
+ /**
+ * Gets the content of the page as raw XML.
+ * @return Page content.
+ */
+ Element getContent();
+
+ /**
+ * Obtains the links available on the page.
+ * @return Link names.
+ */
+ Action[] getActions();
+
+ /**
+ * Gets the named action. Only works if the action name is unique.
+ * @param aName Name of the action.
+ * @return Action object.
+ */
+ Action getAction(String aName);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Exception thrown when there is a problem in retrieving or transforming the
+ * page.
+ */
+public class PageException extends Exception {
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public PageException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aCause Cause of the exception.
+ */
+ public PageException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.w3c.dom.Document;
+
+/**
+ * Represents a specific request to obtain and transform a page.
+ */
+public interface PageRequest {
+
+ /**
+ * Gets a page as an XML document.
+ * @param aUrl Url of the page.
+ * @param aParams Additional parameters to supply.
+ * @param aClient Http client to use.
+ * @return Client.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient) throws PageException;
+
+ /**
+ * Overrides the Xslt to use. This is used when the transformed page specifies
+ * the page type explicitly for an action.
+ * @param aXslt Xslt to use.
+ */
+ void overrideXslt(String aXslt);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Represents the type of a page determining how the HTML should be transformed into
+ * XML.
+ */
+public class PageType {
+
+ /**
+ * Type string.
+ */
+ private String _type;
+
+ /**
+ * Constructs the type.
+ * @param aType Type.
+ */
+ public PageType(String aType) {
+ _type = aType;
+ }
+
+ /**
+ * Gets the type.
+ * @return Type.
+ */
+ public String getType() {
+ return _type;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "PageType(type='" + _type + "')";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PageType)) {
+ return false;
+ }
+ return toString().equals(obj.toString());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.w3c.dom.Document;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Retrieving pages using the post method.
+ */
+public class PostPageRequest extends AbstractPageRequest {
+
+ /**
+ * Constructs the request.
+ * @param aMaxTries Maximum number of retries.
+ * @param aMaxDelay Maximum delay before executing the request.
+ * @param aParams Request parameters to use.
+ * @param aHeaders Request headers to use.
+ * @param aXslt XSLT to use.
+ */
+ public PostPageRequest(int aMaxTries, int aMaxDelay,
+ NameValuePair[] aParams,
+ NameValuePair[] aHeaders,
+ String aXslt, XslTransformer aTransformer) {
+ super(aMaxTries, aMaxDelay, aParams, aHeaders, aXslt, aTransformer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#execute(java.lang.String,
+ * org.apache.commons.httpclient.HttpClient)
+ */
+ public Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient)
+ throws PageException {
+ PostMethod method = new PostMethod(aUrl);
+ method.addParameters(getParameters(aParams));
+ try {
+ return executeMethod(aClient, method);
+ } catch (TransformerException e) {
+ throw new PageException("Transformation problem for url " + aUrl, e);
+ } catch (IOException e) {
+ throw new PageException("Problem getting page " + aUrl, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Action implementation.
+ */
+public class ActionImpl implements Action {
+
+ private Crawler _crawler;
+
+ private Element _content;
+
+ private String _name;
+
+ private String _reference;
+
+ private PageType _type;
+
+ private NameValuePair[] _parameters;
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCrawler
+ * Crawler to use.
+ * @param aContent
+ * Content of the action element in the page where the action
+ * occurs.
+ * @param aName
+ * Name of the action.
+ * @param aReference
+ * URL of the reference.
+ * @param aParameters Parameters to use for the action.
+ */
+ public ActionImpl(Crawler aCrawler, Element aContent, String aName,
+ String aReference, NameValuePair[] aParameters) {
+ _crawler = aCrawler;
+ _content = aContent;
+ _name = aName;
+ _reference = aReference;
+ _type = null;
+ _parameters = aParameters;
+ }
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCrawler
+ * Crawler to use.
+ * @param aContent
+ * Content of the action element in the page where the action
+ * occurs.
+ * @param aName
+ * Name of the action.
+ * @param aReference
+ * URL of the reference.
+ * @param aType
+ * Type of the referenced page.
+ * @param aParameters Parameters to use.
+ */
+ public ActionImpl(Crawler aCrawler, Element aContent, String aName,
+ String aReference, PageType aType, NameValuePair[] aParameters) {
+ _crawler = aCrawler;
+ _content = aContent;
+ _name = aName;
+ _reference = aReference;
+ _type = aType;
+ _parameters = aParameters;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#getName()
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#execute()
+ */
+ public Page execute() throws PageException {
+ if (_type == null) {
+ return _crawler.getPage(_reference, _parameters);
+ }
+ return _crawler.getPage(_reference, _parameters, _type);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#getContent()
+ */
+ public Element getContent() {
+ return _content;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if ( !(obj instanceof ActionImpl )) {
+ return false;
+ }
+ ActionImpl action = (ActionImpl)obj;
+ return _reference.equals(action._reference) &&
+ _type.equals(action._type);
+ }
+}
--- /dev/null
+package org.wamblee.crawler.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.xml.XslTransformer;
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.
+ */
+
+/**
+ * Test application which uses the crawler.
+ */
+public final class App {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private App() {
+ // Empty
+ }
+
+ /**
+ * Runs a test program.
+ *
+ * @param aArgs
+ * Arguments. First argument is the crawler config file name and
+ * second argument is the start url.
+ * @throws Exception
+ * In case of problems.
+ */
+ public static void main(String[] aArgs) throws Exception {
+ String configFileName = aArgs[0];
+ String starturl = aArgs[1];
+
+ ConfigurationParser parser = new ConfigurationParser(new XslTransformer());
+ InputStream configFile = new FileInputStream(new File(configFileName));
+ Configuration config = parser.parse(configFile);
+
+ HttpClient client = new HttpClient();
+ // client.getHostConfiguration().setProxy("localhost", 3128);
+
+ Crawler crawler = new CrawlerImpl(client, config);
+
+ System.out.println("Retrieving: " + starturl);
+ Page page = crawler.getPage(starturl, new NameValuePair[0]);
+ showPage(page);
+ page = page.getAction("channels-favorites").execute();
+ recordInterestingShows(page);
+ showPage(page);
+ page = page.getAction("Nederland 1").execute();
+ showPage(page);
+ page = page.getAction("right-now").execute();
+ showPage(page);
+ page = page.getAction("Het elfde uur").execute();
+ showPage(page);
+ }
+
+ /**
+ * @param starturl
+ * @param crawler
+ */
+ private static void showPage(Page aPage) {
+ Action[] links = aPage.getActions();
+ for (Action link : links) {
+ System.out.println("Link found '" + link.getName() + "'");
+ }
+ Element element = aPage.getContent();
+ System.out.println("Retrieved content: " + element.asXML());
+ }
+
+ private static void recordInterestingShows(Page page) throws PageException {
+ Action[] channels = page.getActions();
+ for (Action channel : channels) {
+ examineChannel(channel.getName(), channel.execute().getAction(
+ "right-now").execute());
+ }
+ }
+
+ private static void examineChannel(String aChannel, Page aPage)
+ throws PageException {
+ Action[] programs = aPage.getActions();
+ for (Action program : programs) {
+ System.out.println(aChannel + " - " + program.getName());
+ if (program.getName().toLowerCase().matches(".*babe.*")) {
+ Page programPage = program.execute();
+ Action record = programPage.getAction("record");
+ System.out.println("Recording possible: " + record != null);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.util.regex.Pattern;
+
+/**
+ * Configuration item for obtaining an object in case a pattern matches.
+ */
+class ConfigItem<ValueType> {
+
+ private Pattern _pattern;
+
+ private ValueType _value;
+
+ /**
+ * Constructs the item.
+ * @param aPattern Pattern.
+ * @param aValue Value.
+ */
+ protected ConfigItem(String aPattern, ValueType aValue) {
+ _pattern = Pattern.compile(aPattern);
+ _value = aValue;
+ }
+
+ /**
+ * Returns the object in case the value matches.
+ * @param aValue Value to match.
+ * @return Object in case there is a match, null otherwise.
+ */
+ protected ValueType match(String aValue) {
+ if (!_pattern.matcher(aValue).matches()) {
+ return null;
+ }
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.util.List;
+
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Implementation of the configuration for the crawler.
+ */
+public class ConfigurationImpl implements Configuration {
+
+ private List<UrlConfig> _urlConfig;
+
+ private List<PageTypeConfig> _pageTypeConfig;
+
+ /**
+ * Constructs the configuration.
+ * @param aUrlConfig List of URL configuration elements.
+ * @param aPageTypeConfig List of page type configuration elements.
+ */
+ public ConfigurationImpl(List<UrlConfig> aUrlConfig,
+ List<PageTypeConfig> aPageTypeConfig) {
+ _urlConfig = aUrlConfig;
+ _pageTypeConfig = aPageTypeConfig;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Configuration#getRequest(java.lang.String)
+ */
+ public PageRequest getRequest(String aUrl) {
+
+ for (UrlConfig config : _urlConfig) {
+ PageRequest request = config.getRequest(aUrl);
+ if (request != null) {
+ return request;
+ }
+ }
+ throw new RuntimeException("No configuration matched the URL '" + aUrl
+ + "'");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Configuration#getRequest(org.wamblee.crawler.PageType)
+ */
+ public PageRequest getRequest(PageType aType) {
+ for (PageTypeConfig config : _pageTypeConfig) {
+ PageRequest request = config.getRequest(aType.getType());
+ if (request != null) {
+ return request;
+ }
+ }
+ throw new RuntimeException("No configuration matched type '" + aType
+ + "'");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.GetPageRequest;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PostPageRequest;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Parsing of the configuration from an XML file.
+ */
+public class ConfigurationParser {
+
+ private static final String ELEM_URL = "url";
+
+ private static final String ELEM_TYPE = "type";
+
+ private static final String ELEM_PATTERN = "pattern";
+
+ private static final String ELEM_METHOD = "method";
+
+ private static final String ELEM_XSLT = "xslt";
+
+ private static final String ELEM_PARAM = "param";
+
+ private static final String ELEM_HEADER = "header";
+
+ private static final String AT_NAME = "name";
+
+ private static final String AT_VALUE = "value";
+
+ private static final String METHOD_POST = "post";
+
+ private static final String METHOD_GET = "get";
+
+ private static final int MAX_TRIES = 3;
+
+ private static final int MAX_DELAY = 10000;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the configuration parser.
+ */
+ public ConfigurationParser(XslTransformer aTransformer) {
+ _transformer = aTransformer;
+ }
+
+ /**
+ * Parses the configuration from an input stream.
+ * @param aStream Input file.
+ * @return Configuration.
+ */
+ public Configuration parse(InputStream aStream) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(aStream);
+
+ Element root = document.getRootElement();
+ List<UrlConfig> urlConfigs = parseUrlConfigs(root);
+ List<PageTypeConfig> pageTypeConfigs = parsePageTypeConfigs(root);
+ return new ConfigurationImpl(urlConfigs, pageTypeConfigs);
+ } catch (DocumentException e) {
+ throw new RuntimeException("Problem parsing config file", e);
+ }
+ }
+
+ /**
+ * Parses the URL-based configuration.
+ * @param aRoot Root of the configuration file document.
+ * @return List of URL-based configurations.
+ */
+ private List<UrlConfig> parseUrlConfigs(Element aRoot) {
+ List<UrlConfig> configs = new ArrayList<UrlConfig>();
+ for (Iterator i = aRoot.elementIterator(ELEM_URL); i.hasNext();) {
+ Element url = (Element) i.next();
+ UrlConfig config = parseUrlConfig(url);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ /**
+ * Parses the page type based configurations.
+ * @param aRoot Root of the configuration file document.
+ * @return LIst of page type based configurations.
+ */
+ private List<PageTypeConfig> parsePageTypeConfigs(Element aRoot) {
+ List<PageTypeConfig> configs = new ArrayList<PageTypeConfig>();
+ for (Iterator i = aRoot.elementIterator(ELEM_TYPE); i.hasNext();) {
+ Element url = (Element) i.next();
+ PageTypeConfig config = parsePageTypeConfig(url);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ /**
+ * Parses a URL-based configuration.
+ * @param aUrlElem Configuration element.
+ * @return Configuration.
+ */
+ private UrlConfig parseUrlConfig(Element aUrlElem) {
+ String pattern = aUrlElem.elementText(ELEM_PATTERN);
+ PageRequest request = parseRequestConfig(aUrlElem);
+ return new UrlConfig(pattern, request);
+ }
+
+ /**
+ * Parses a page type based configuration.
+ * @param aTypeElem Configuration element.
+ * @return Configuration.
+ */
+ private PageTypeConfig parsePageTypeConfig(Element aTypeElem) {
+ String pattern = aTypeElem.elementText(ELEM_PATTERN);
+ PageRequest request = parseRequestConfig(aTypeElem);
+ return new PageTypeConfig(pattern, request);
+ }
+
+ /**
+ * Parses a request configuration describing how to execute requests.
+ * @param aElem Configuration element.
+ * @return Page request.
+ */
+ private PageRequest parseRequestConfig(Element aElem) {
+ String method = aElem.elementText(ELEM_METHOD);
+ String xslt = aElem.elementText(ELEM_XSLT);
+ List<NameValuePair> params = parseNameValuePairs(aElem, ELEM_PARAM);
+ List<NameValuePair> headers = parseNameValuePairs(aElem, ELEM_HEADER);
+
+ NameValuePair[] paramsArray = params.toArray(new NameValuePair[0]);
+ NameValuePair[] headersArray = headers.toArray(new NameValuePair[0]);
+ PageRequest request;
+ if (METHOD_POST.equals(method)) {
+ request = new PostPageRequest(MAX_TRIES, MAX_DELAY, paramsArray, headersArray,
+ xslt, _transformer);
+ } else if (METHOD_GET.equals(method) || method == null) {
+ request = new GetPageRequest(MAX_TRIES, MAX_DELAY, paramsArray, headersArray,
+ xslt, _transformer);
+ } else {
+ throw new RuntimeException("Unknown request method '" + method
+ + "'. Only " + METHOD_GET + " and " + METHOD_POST
+ + " are supported");
+ }
+ return request;
+ }
+
+ /**
+ * @param aElem
+ * @return
+ */
+ private List<NameValuePair> parseNameValuePairs(Element aElem, String aElemName) {
+ List<NameValuePair> headers = new ArrayList<NameValuePair>();
+ for (Iterator i = aElem.elementIterator(aElemName); i.hasNext();) {
+ Element paramElem = (Element) i.next();
+ NameValuePair header = parseParameter(paramElem);
+ headers.add(header);
+ }
+ return headers;
+ }
+
+ /**
+ * Parses a parameter definition.
+ * @param aParam Parameter.
+ * @return Name value pair describing a parameter.
+ */
+ private NameValuePair parseParameter(Element aParam) {
+ String name = aParam.attributeValue(AT_NAME);
+ String value = aParam.attributeValue(AT_VALUE);
+ return new NameValuePair(name, value);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+import org.dom4j.io.DOMReader;
+import org.w3c.dom.Document;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Crawler implementation.
+ */
+public class CrawlerImpl implements Crawler {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerImpl.class);
+
+ private HttpClient _client;
+
+ private Configuration _config;
+
+ /**
+ * Constructs the crawler.
+ *
+ * @param aClient
+ * Http client to use.
+ * @param aConfig
+ * Configuration.
+ */
+ public CrawlerImpl(HttpClient aClient, Configuration aConfig) {
+ _client = aClient;
+ _config = aConfig;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Crawler#getPage(java.lang.String)
+ */
+ public Page getPage(String aUrl, NameValuePair[] aParams) throws PageException {
+ LOG.debug("Getting page: url = '" + aUrl + "'");
+ PageRequest request = _config.getRequest(aUrl);
+ Document content = request.execute(aUrl, aParams, _client);
+ return transformToDom4jDoc(aUrl, content);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Crawler#getPage(java.lang.String,
+ * java.lang.String)
+ */
+ public Page getPage(String aUrl, NameValuePair[] aParams, PageType aType) throws PageException {
+ LOG.debug("Getting page: url = '" + aUrl + "', type = '" + aType + "'");
+ PageRequest request = _config.getRequest(aType);
+ Document content = request.execute(aUrl, aParams, _client);
+ return transformToDom4jDoc(aUrl, content);
+ }
+
+ /**
+ * Converts a w3c DOM document to a page object.
+ * @param content DOM document.
+ * @return
+ */
+ private Page transformToDom4jDoc(String aUrl, Document content) {
+ DOMReader reader = new DOMReader();
+ org.dom4j.Document dom4jDoc = reader.read(content);
+ Element root = dom4jDoc.getRootElement();
+ dom4jDoc.remove(root);
+
+ return new PageImpl(aUrl, this, replaceReferencesWithContent(root));
+ }
+
+ /**
+ * Perform crawling. Find references in the retrieved content and replace
+ * them by the content they refer to by retrieving the appropriate pages as
+ * well.
+ *
+ * @param content
+ * Content which must be made complete.
+ * @return Fully processed content.
+ */
+ private Element replaceReferencesWithContent(Element content) {
+ return content; // TODO implement.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.XPath;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Page implementation.
+ */
+public class PageImpl implements Page {
+
+ private static final String ELEM_NAME = "action";
+
+ private static final String ATT_NAME = "name";
+
+ private static final String ATT_HREF = "reference";
+
+ private static final String ATT_TYPE = "type";
+
+ private static final String ELEM_PARAM = "param";
+
+ private static final String ATT_VALUE = "value";
+
+ private String _href;
+
+ private Crawler _crawler;
+
+ private Element _content;
+
+ private Action[] _actions;
+
+ /**
+ * Constructs a page.
+ *
+ * @param aContent
+ */
+ public PageImpl(String aHref, Crawler aCrawler, Element aContent) {
+ _href = aHref;
+ _crawler = aCrawler;
+ _content = aContent;
+ _actions = computeActions();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getLinkNames()
+ */
+ private Action[] computeActions() {
+ XPath xpath = DocumentHelper.createXPath(ELEM_NAME);
+ List<Element> results = (List<Element>) xpath.selectNodes(_content);
+ List<Action> names = new ArrayList<Action>();
+ for (Element elem : results) {
+ String name = elem.attributeValue(ATT_NAME);
+ String href = elem.attributeValue(ATT_HREF);
+ String type = elem.attributeValue(ATT_TYPE);
+ NameValuePair[] params = getMandatoryParameters(elem);
+ href = absolutizeHref(_href, href);
+ if (type == null) {
+ names.add(new ActionImpl(_crawler, elem, name, href, params));
+ } else {
+ names.add(new ActionImpl(_crawler, elem, name, href,
+ new PageType(type), params));
+ }
+ }
+ return names.toArray(new Action[0]);
+ }
+
+ /**
+ * Absolutize the hyperlink
+ * @param aPageHref Absolute page reference.
+ * @param aLinkHref Possibly relative link reference.
+ * @return Absolute hyperlink.
+ */
+ private String absolutizeHref(String aPageHref, String aLinkHref) {
+
+ try {
+ URL pageUrl = new URL(aPageHref);
+ URL newUrl = new URL(pageUrl, aLinkHref);
+ return newUrl.toString(); // TODO need to use URL instead of String throughout the code.
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Malformed URL", e);
+ }
+ }
+
+ private NameValuePair[] getMandatoryParameters(Element aAction) {
+ List<NameValuePair> result = new ArrayList<NameValuePair>();
+ for (Element param: (List<Element>)aAction.elements(ELEM_PARAM)) {
+ String name = param.attributeValue(ATT_NAME);
+ String value = param.attributeValue(ATT_VALUE);
+ result.add(new NameValuePair(name, value));
+ }
+ return result.toArray(new NameValuePair[0]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getContent()
+ */
+ public Element getContent() {
+ return _content;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getActions()
+ */
+ public Action[] getActions() {
+ return _actions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getAction(java.lang.String)
+ */
+ public Action getAction(String aName) {
+ List<Action> results = new ArrayList<Action>();
+ for (Action action : _actions) {
+ if (action.getName().equals(aName)) {
+ results.add(action);
+ }
+ }
+ if (results.size() == 0) {
+ return null;
+ }
+ if (results.size() > 1) {
+ throw new RuntimeException("Duplicate action '" + aName + "'");
+ }
+ return results.get(0);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.wamblee.crawler.PageRequest;
+
+/**
+ * Page type configuration.
+ */
+public class PageTypeConfig extends ConfigItem<PageRequest> {
+
+ /**
+ * Constructs the configuration.
+ * @param aPattern Page type pattern.
+ * @param aRequest Page request.
+ */
+ public PageTypeConfig(String aPattern, PageRequest aRequest) {
+ super(aPattern, aRequest);
+ }
+
+ /**
+ * Returns the request in case the type matches.
+ * @param aType Page type.
+ * @return Request if the type matches, null otherwise.
+ */
+ public PageRequest getRequest(String aType) {
+ return match(aType);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.wamblee.crawler.PageRequest;
+
+/**
+ * Represents the configuration for specific URLs.
+ */
+public class UrlConfig extends ConfigItem<PageRequest> {
+ /**
+ * Constructs the information for how to perform a request for a specific
+ * URL.
+ *
+ * @param aPattern
+ * Pattern that the URL must match.
+ * @param aRequest
+ * Request that must be executed to retrieve the URL.
+ */
+ public UrlConfig(String aPattern, PageRequest aRequest) {
+ super(aPattern, aRequest);
+ }
+
+ /**
+ * Gets the request to execute.
+ *
+ * @return Request, or null if the URL does not match.
+ */
+ public PageRequest getRequest(String aUrl) {
+ return match(aUrl);
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides the implementations of the web crawler interfaces.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides the basic interfaces for a web crawler.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY delegator SYSTEM "file:../build/delegator.xml">
+]>
+
+<project name="utils" basedir=".">
+
+ <property name="project.home" value=".."/>
+
+ &header;
+
+ <property name="projects" value="basic,kiss,kissweb"/>
+
+ &delegator;
+
+</project>
--- /dev/null
+<assembly>
+ <id>kissbin</id>
+ <formats>
+ <format>dir</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <moduleSets>
+ <moduleSet>
+ <includes>
+ <include>org.wamblee:crawler-kiss</include>
+ </includes>
+ <binaries>
+ <includeDependencies>true</includeDependencies>
+ <outputDirectory>lib</outputDirectory>
+ <unpack>false</unpack>
+ </binaries>
+ </moduleSet>
+
+ </moduleSets>
+
+ <fileSets>
+ <fileSet>
+ <directory>kiss/conf/kiss</directory>
+
+ <outputDirectory>conf</outputDirectory>
+ <includes>
+ <include>config.xml.example</include>
+ <include>programs.xml</include>
+ <include>org.wamblee.crawler.properties</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>kiss/conf/kiss</directory>
+
+ <outputDirectory>bin</outputDirectory>
+ <includes>
+ <include>run.*</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>kiss/target</directory>
+
+ <outputDirectory>lib</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+ <dependencySets>
+ <dependencySet>
+ <scope>runtime</scope>
+ </dependencySet>
+ </dependencySets>
+
+
+</assembly>
--- /dev/null
+This is a crawler for the KiSS Electronic Program Guide that can be used for instance with the KiSS DP558 hard-disc recorder. It uses the basic crawler for its implementation.
+
+Based on preferences for recording programs, the crawler automatically records programs that are scheduled to run on the same day. This saves a lot of manual work in recording programs.
+
+The final idea is to define ones own interests in television programs and have the crawler record them automatically or send notifications of possibly interesting programs. Whether programs should be recorded can be determined by several criteria such as program title, channel, time of day, and keywords in the description.
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../../build/trailer.xml">
+ <!ENTITY crawlerdeps SYSTEM "file:../basic/deps.xml">
+ <!ENTITY deps SYSTEM "file:deps.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value="../.."/>
+ <property name="module.name" value="wamblee-crawler-kiss" />
+
+ &header;
+ &crawlerdeps;
+ &deps;
+
+ <target name="module.build.deps"
+ depends="kisscrawler.src.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="kisscrawler.test.d">
+ </target>
+
+ <property name="post-dist-lite" value="yes"/>
+
+ <target name="post-dist-lite" depends="init_directory_properties">
+ <property name="module.install.dir" value="${module.build.dir}/kiss-crawler"/>
+
+ <mkdir dir="${module.install.dir}"/>
+ <mkdir dir="${module.install.dir}/bin"/>
+ <copy todir="${module.install.dir}/bin">
+ <fileset dir="conf/kiss">
+ <include name="run.*"/>
+ </fileset>
+ </copy>
+
+ <mkdir dir="${module.install.dir}/conf"/>
+ <copy todir="${module.install.dir}/conf">
+ <fileset dir="conf/kiss">
+ <include name="config.xml.example"/>
+ <include name="programs.xml"/>
+ <include name="org.wamblee.crawler.properties"/>
+ </fileset>
+ </copy>
+
+ <mkdir dir="${module.install.dir}/lib"/>
+ <copy todir="${module.install.dir}/lib" flatten="yes">
+ <fileset dir="${external.lib.dir}" includes="*.jar"/>
+ <fileset dir="${module.build.dir}" includes="**/*.jar"/>
+ </copy>
+
+ <zip destfile="${module.dist.dir}/kiss-crawler-bin.zip"
+ basedir="${module.install.dir}"/>
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<crawler>
+
+ <type>
+ <pattern>login</pattern>
+ <method>post</method>
+ <xslt>login-graphic.xsl</xslt>
+ <param name="user" value="youruserid"/>
+ <param name="passwd" value="yourpassword"/>
+ <param name="GMode" value="GraphicMode"/>
+ <param name="SavePlayerID" value="1"/>
+ <param name="submit" value="Login"/>
+ <header name="Referer" value="http://epg.kml.kiss-technology.com/login.php"/>
+ </type>
+
+ <type>
+ <pattern>channels-favorites</pattern>
+ <xslt>channels-favorites-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>channel-overview</pattern>
+ <xslt>channel-overview.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>right-now</pattern>
+ <xslt>channel-right-now-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>tomorrow</pattern>
+ <xslt>channel-right-now-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>program-info</pattern>
+ <xslt>program-info-mobile.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>recorded</pattern>
+ <xslt>recorded.xsl</xslt>
+ </type>
+
+ <url>
+ <pattern>http://epg.kml.kiss-technology.com/login.php</pattern>
+ <xslt>mainpage.xsl</xslt>
+ </url>
+
+ <url>
+ <pattern>.*</pattern>
+ <method>get</method>
+ <xslt>identity.xsl</xslt>
+ </url>
+</crawler>
+
--- /dev/null
+
+
+############################################################################
+# Mail server configuration
+############################################################################
+org.wamblee.crawler.smtp.host=falcon
+org.wamblee.crawler.smtp.port=25
+org.wamblee.crawler.smtp.username=
+org.wamblee.crawler.smtp.password=
+
+############################################################################
+# Mail notification configuration
+############################################################################
+org.wamblee.crawler.notification.from=kiss@wamblee.org
+org.wamblee.crawler.notification.to=erik@bladibla.org
+org.wamblee.crawler.notification.subject=Recording summary for tomorrow
+
--- /dev/null
+<programs>
+
+ <program>
+ <category>horror</category>
+ <action>notify</action>
+ <match field="description">horror</match>
+ </program>
+
+ <program>
+ <category>horror</category>
+ <action>notify</action>
+ <match>the.*ghost.*whisperer</match>
+ </program>
+
+ <program>
+ <category>films</category>
+ <action>notify</action>
+ <match field="keywords">film</match>
+ <match field="description">horror|actie|thriller</match>
+ </program>
+
+ <program>
+ <category>wetenschap</category>
+ <action>notify</action>
+ <match field="description">wetenschap</match>
+ </program>
+
+ <program>
+ <category>science fiction</category>
+ <action>notify</action>
+ <match field="description">sf-|(sci-fi)|(science fiction)</match>
+ </program>
+
+ <program>
+ <match>invasion</match>
+ </program>
+
+ <program>
+ <action>notify</action>
+ <category>documentaires</category>
+ <match>(zembla)|(uur.*wolf)|(andere tijden)|(de.*leugen.*regeert)</match>
+ </program>
+
+ <program>
+ <priority>20</priority>
+ <match>star.*gate</match>
+ </program>
+
+ <program>
+ <match>six.*feet.*under</match>
+ </program>
+
+ <program>
+ <priority>11</priority>
+ <match>battlestar</match>
+ </program>
+
+ <program>
+ <priority>10</priority>
+ <match>star trek</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>((dr)|(doct.*)).*who</match>
+ </program>
+
+ <program>
+ <priority>8</priority>
+ <match>little britain</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>the.*x.*files</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>buffy.*vampire.*slayer</match>
+ </program>
+
+ <program>
+ <action>notify</action>
+ <match>de.*wereld.*draait.*door</match>
+ </program>
+
+ <program>
+ <priority>8</priority>
+ <match>jag</match>
+ </program>
+
+ <program>
+ <priority>5</priority>
+ <match>shouf shouf</match>
+ </program>
+
+ <program>
+ <match>red dwarf</match>
+ </program>
+
+ <program>
+ <match>top gear</match>
+ </program>
+
+ <program>
+ <match>bedreigde.*paradijzen</match>
+ </program>
+
+ <program>
+ <match>wie is de baas</match>
+ </program>
+
+ <program>
+ <category>wetenschap</category>
+ <action>notify</action>
+ <match>brainiac</match>
+ </program>
+
+ <program>
+ <category>auto</category>
+ <match>wegmisbruikers|(blik.*op.*weg)</match>
+ </program>
+
+</programs>
--- /dev/null
+\r
+\r
+\r
+cd ../conf\r
+\r
+\r
+set CP=../lib/wamblee-crawler-kiss.jar;../lib/activation.jar;../lib/comons-beanutils-1.7.0.jar;../lib/commons-codec-1.3.jar\r
+set CP=%CP%;../lib/commons-email-1.0.jar;../lib/commons-httpclient-3.0.jar;../lib/commons-logging-1.0.2.jar;\r
+set CP=%CP%;../lib/dom4j-1.6.jar;../lib/jaxen-1.1-beta-4.jar;../lib/jtidy-4aug2000r7-dev.jar;log4j-1.2.9.jar;\r
+set CP=%CP%;../lib/mail.jar;../lib/spring-1.2.5.jar;../lib/wamblee-crawler-basic.jar;../lib/wamblee-crawler-kiss.jar;\r
+set CP=%CP%;../lib/wamblee-support.jar;../lib/xerces-2.4.0.jar\r
+\r
+\r
+java -classpath %CP% org.wamblee.crawler.kiss.main.KissCrawler config.xml programs.xml
+
--- /dev/null
+#!/bin/ksh
+
+cd $( dirname $0 )/../conf
+
+CP="."
+for i in ../lib/*.jar
+do
+ CP="$i:$CP"
+done
+
+set -x
+java -classpath $CP org.wamblee.crawler.kiss.main.KissCrawler \
+ config.xml programs.xml
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<title></title>
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Nederland 1 *</title>
+</head>
+<body bgcolor="#ffffff">
+d> <img src="../images/KiSS_Logo_small.gif"
+align="center" />
+<h1> Nederland 1 *</h1>
+
+<h2>What's on?</h2>
+
+<a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=0&now=1&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Right now</a> - 12:11, Monday 13th March<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=20&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)1~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Evening</a> - Starting 20:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=16&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)2~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Afternoon</a> - Starting 16:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=12&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)3~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Noon</a> - Starting 12:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=6&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)4~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Morning</a> - Starting 6:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=36&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Tomorrow</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=60&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)8~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Wednesday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=84&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)9~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Thursday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=108&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)10~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Friday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=132&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)11~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Saturday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=156&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)12~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Sunday</a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&page=0&station=0&tz=1&sel=0&back=$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:11, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<meta name="generator" content="Adobe GoLive" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<script language="JavaScript1.2" src="../popup15.js"
+type="text/javascript">
+</script>
+
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<div id="popuptitle"
+style="visibility:hidden;position:absolute;z-index:1000;top:-100">
+</div>
+
+<script language="JavaScript1.2" type="text/javascript">
+ Style[0]=["#ffffff","#6f7e8f","","","font-family:arial,helvetica,geneva,swiss,sunsans-regular",2,"#183457","#d0dce8","","","font-family:arial,helvetica,geneva,swiss,sunsans-regular",1,,,2,"#92AAC6",4,,,,,"",,,,]
+ var TipId="popuptitle"
+ var FiltersEnabled = 1
+ mig_clay()
+
+</script>
+
+<h1><img src="../images/KiSS_Logo.gif" align="right" /><a
+href="tvstart.php"><img border="0"
+src="../images/tvguide_logo.gif" /></a></h1>
+
+<br />
+
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td align="left">
+<h1><img
+src="http://epg.kml.kiss-technology.com/tv/logos/6_w60.gif"
+align="middle" hspace="6" /> Nederland 1</h1>
+
+<p></p>
+</td>
+</tr>
+</table>
+
+<table align="center" style="width: 400px; text-align: left;"
+border="0" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+<td style="vertical-align: bottom; width: 400px;"><img
+src="../images/tabs_none.gif" border="0" alt=""
+usemap="#tabs_Map" /><map id="tabs_Map" name="tabs_Map">
+<area shape="poly" alt="What's On Now?"
+coords="126,30, 118,0, 0,0, 0,30"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?reload=1&mode=3&station=0&view=0&now=1"
+ class="grid" />
+<area shape="poly" alt="Favorite Shows"
+coords="239,0, 247,30, 128,30, 120,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+<area shape="poly" alt="Movies"
+coords="241,0, 250,30, 309,30, 300,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+<area shape="poly" alt="Sports"
+coords="302,0, 311,30, 370,30, 362,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+</map> </td>
+</tr>
+</tbody>
+</table>
+
+<table class="tvlist" align="center" width="400" border="0"
+cellspacing="0" cellpadding="1">
+<tr height="25">
+<td align="left" bgcolor="black" colspan="2"><font
+color="white"><b> Wednesday 23rd </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 23:15 -
+00:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[0]=["Karels keuze","23:15 - 00:00<br>Documentaire Het schokkende verhaal van een Turkse familie uit Denemarken, die verscheurd raakt door eerwraak. Een minutieuze reconstructie van een hartverscheurend drama, verteld door het meisje om wie de ruzie begon en haar broer, die door haar vader gedwongen werd de trekker over te halen. Hij was toen dertien jaar en moest meedoen omdat hij te jong was om veroordeeld te kunne [...]"]
+</script>
+
+ <a onmouseover="stm(Text[0],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787582&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Karels keuze</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:00 -
+00:30 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[1]=["Nederland helpt","00:00 - 00:30<br>Serie reportages over Nederlanders, bekend of onbekend, die zich inzetten voor een goed doel."]
+</script>
+
+ <a onmouseover="stm(Text[1],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787583&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland helpt</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 00:30 -
+00:55 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[2]=["Man beet hond","00:30 - 00:55"]
+</script>
+
+ <a onmouseover="stm(Text[2],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787584&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:55 -
+06:45 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[3]=["Nacht-tv: Netwerk herhaling","00:55 - 06:45"]
+</script>
+
+ <a onmouseover="stm(Text[3],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787585&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 06:45 -
+07:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[4]=["Nederland in beweging","06:45 - 07:00<br>Gymnastiek met als doel Nederlanders in beweging te krijgen. Met voedingstips, de favoriete sport van een bekende Nederlander en sportieve uitgaanstips."]
+</script>
+
+ <a onmouseover="stm(Text[4],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849473&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 07:00 -
+09:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[5]=["NOS-Journaal","07:00 - 09:00<br>Gevolgd door herhalingen."]
+</script>
+
+ <a onmouseover="stm(Text[5],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849474&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:00 -
+09:10 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[6]=["NOS-Journaal","09:00 - 09:10"]
+</script>
+
+ <a onmouseover="stm(Text[6],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849475&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:10 -
+09:30 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[7]=["Nederland in beweging","09:10 - 09:30<br>Gymnastiek met als doel Nederlanders in beweging te krijgen. Met voedingstips, de favoriete sport van een bekende Nederlander en sportieve uitgaanstips."]
+</script>
+
+ <a onmouseover="stm(Text[7],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849476&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:30 -
+09:55 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[8]=["That's the question","09:30 - 09:55<br>Quiz waarin twee deelnemers zo snel mogelijk een vraag moeten raden. Aan de hand van het beantwoorden van vragen worden er letters van de vraag zichtbaar. Met het goed beantwoorden van de vragen verdient de kandidaat seconden waarmee hij/zij later de finale speelt."]
+</script>
+
+ <a onmouseover="stm(Text[8],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849477&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:55 -
+11:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[9]=["TROS Zomeravondcafé","09:55 - 11:00<br>Live programma vanaf het binnenplein van het TROS-gebouw. Iedere dag staat in het teken van een ander zomers thema. Bert Kuizenga praat met een bekende hoofdgast, die aansluit bij het thema van de dag. De actualiteit wordt behandeld door Angela Esajas. Verder twee muzikale acts en de kookrubriek met Ad Janssen. Bovendien is er twee keer per week een optreden van psycho-illusionis [...]"]
+</script>
+
+ <a onmouseover="stm(Text[9],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849478&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>TROS Zomeravondcafé</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 11:00 -
+11:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[10]=["Andere tijden","11:00 - 11:30<br>Geschiedenismagazine Bij het hardrijden op de schaats deed Nederland tijdens de Olympische Winterspelen weer op veel fronten mee, maar bij het kunstrijden hadden we niets meer te zoeken. Dat is wel eens anders geweest; in 1964 won Sjoukje Dijkstra in Innsbrück een gouden medaille. Over haar en haar hartsvriendin èn rivale Joan Haanappel gaat deze aflevering."]
+</script>
+
+ <a onmouseover="stm(Text[10],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849479&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Andere tijden</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 11:30 -
+12:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[11]=["De vloer op","11:30 - 12:00<br>Acteurs improviseren in het Bimhuis. -Vervangster gezocht. Met Carly Wijs, Hadewych Minis en Gijs Scholten van Aschat. Carly heeft nog ongeveer twee maanden te leven. Ze gaat naar de secretaresse van haar man en vraagt haar of zij na haar dood haar plaats wil innemen. In alle opzichten. -Burka zoekt imam. Met Leopold Witte en Pierre Bokma. Pierre is burgemeester van een kleine Li [...]"]
+</script>
+
+ <a onmouseover="stm(Text[11],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849480&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De vloer op</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:00 -
+12:10 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[12]=["NOS-Journaal","12:00 - 12:10"]
+</script>
+
+ <a onmouseover="stm(Text[12],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849481&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 12:10 -
+12:40 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[13]=["Man beet hond","12:10 - 12:40<br>Het beste van het afgelopen jaar."]
+</script>
+
+ <a onmouseover="stm(Text[13],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849482&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:40 -
+13:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[14]=["Lingo","12:40 - 13:00<br>Woordspel waarin spelers zesletterwoorden moeten raden en deelnemers aan de Postcode Loterij met de PostcodeLingo-kaart mee kunnen spelen en waarin de bekendmaking van de dagpostcodes voor de Lingospelers thuis plaatsvindt."]
+</script>
+
+ <a onmouseover="stm(Text[14],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849483&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Lingo</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:00 -
+13:10 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[15]=["NOS-Journaal","13:00 - 13:10"]
+</script>
+
+ <a onmouseover="stm(Text[15],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849484&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 13:10 -
+13:15 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[16]=["NOS-Sportjournaal","13:10 - 13:15"]
+</script>
+
+ <a onmouseover="stm(Text[16],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849485&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Sportjournaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:15 -
+14:05 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[17]=["Warzone: Congo","13:15 - 14:05<br>Documentaire waarin het leven van jongeren in conflictgebieden op een indringende manier in beeld wordt gebracht. In Congo worden Gideon van Aartsen en zijn 25-jarige reisgenote Nynke Douma geconfronteerd met tienermoeders en heksenkinderen. In dit land vinden veel verkrachtingen plaats, waardoor de grote hoeveelheid tienermoeders kan worden verklaart. Ook onderzoeken ze het feno [...]"]
+</script>
+
+ <a onmouseover="stm(Text[17],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849486&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Warzone: Congo</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 14:05 -
+14:35 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[18]=["Wroeten","14:05 - 14:35<br>12-delige Tuinprogramma waarin Arjan Ederveen bevlogen verslag uitbrengt van wat er gebeurt in de tuin bij zijn boerderij: er groeien gewone en ongewone groenten, er bloeien bloemen en planten en elke week wordt er een logeerdier gebracht door een boer die wel eens met vakantie wil. Arjan wordt bijgestaan door Heilien Tonckens, expert in ecologisch tuinieren, en tuinman Hans Enge [...]"]
+</script>
+
+ <a onmouseover="stm(Text[18],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849487&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wroeten</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 14:35 -
+15:14 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[19]=["Spraakmakende zaken","14:35 - 15:14<br>8-delige Serie gesprekken over actuele kwesties. Paul Rosenmöller ontvangt zijn gasten in Paviljoen Het Oosten in Amsterdam, waar hij met hen praat over brandende kwesties uit het recente verleden. Het is behoorlijk bijzonder dat SGP-leider Bas van der Vlies tekst en uitleg komt geven over de vrouwenkwestie in de SGP. SGP-ers zijn uit overtuiging immers niet zo dol op telev [...]"]
+</script>
+
+ <a onmouseover="stm(Text[19],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849488&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Spraakmakende zaken</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 15:14 -
+15:16 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[20]=["Wilde Ganzen","15:14 - 15:16"]
+</script>
+
+ <a onmouseover="stm(Text[20],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849489&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wilde Ganzen</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 15:16 -
+16:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[21]=["Opsporing verzocht","15:16 - 16:00<br>Opsporingsprogramma i.s.m. politie en justitie voor een veiliger samenleving."]
+</script>
+
+ <a onmouseover="stm(Text[21],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849490&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Opsporing verzocht</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 16:00 -
+16:05 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[22]=["NOS-Journaal","16:00 - 16:05"]
+</script>
+
+ <a onmouseover="stm(Text[22],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849491&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 16:05 -
+17:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[23]=["Denkend aan Showroom","16:05 - 17:00<br>Herinneringen aan de kleurrijke mensen uit het spraakmakende programma Showroom (1977-1983)."]
+</script>
+
+ <a onmouseover="stm(Text[23],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849492&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Denkend aan Showroom</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:00 -
+17:05 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[24]=["NOS-Journaal","17:00 - 17:05"]
+</script>
+
+ <a onmouseover="stm(Text[24],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849493&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 17:05 -
+17:35 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[25]=["God voor...","17:05 - 17:35<br>Snel interviewprogramma over langzame zaken. Surinamers over feesten en God."]
+</script>
+
+ <a onmouseover="stm(Text[25],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849494&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>God voor...</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:35 -
+18:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[26]=["Himalaya with Michael Palin","17:35 - 18:30<br>6-delige Serie reportages Nadat Michael Palin eerder de uitdagingen van zeeën, polen en woestijnen is aangegaan, vormen de hoogste bergen ter wereld voor hem een natuurlijk doel. Hij volgt de Himalaya over de hele lengte van het gebergte, van de grens tussen Pakistan en Afghanistan door India, Nepal en Tibet en Yunnan in China. Waarna hij opnieuw de bergen oversteekt, naar A [...]"]
+</script>
+
+ <a onmouseover="stm(Text[26],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849495&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Himalaya with Michael Palin</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 18:30 -
+19:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[27]=["That's the question","18:30 - 19:00<br>Quiz waarin twee deelnemers zo snel mogelijk een vraag moeten raden. Aan de hand van het beantwoorden van vragen worden er letters van de vraag zichtbaar. Met het goed beantwoorden van de vragen verdient de kandidaat seconden waarmee hij/zij later de finale speelt."]
+</script>
+
+ <a onmouseover="stm(Text[27],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849496&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 19:00 -
+19:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[28]=["Man beet hond","19:00 - 19:30<br>Het beste van het afgelopen jaar."]
+</script>
+
+ <a onmouseover="stm(Text[28],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849497&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 19:30 -
+20:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[29]=["De confrontatie","19:30 - 20:00<br>Edwin is een goeduitziende jongen van twintig jaar. Hij heeft een aangeboren botafwijking en mist daardoor zijn bovenbenen. Hij kan kleine stukjes lopen, maar is in principe rolstoelgebonden. Vanaf zijn dertiende is Edwin rolstoelbasketbal gaan spelen. Eerst nog op lokaal niveau, maar na een inschrijving voor selectietrainingen in 2005 werd hij gescout voor het Nederlandse nationa [...]"]
+</script>
+
+ <a onmouseover="stm(Text[29],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849498&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De confrontatie</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 20:00 -
+20:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[30]=["NOS-Journaal","20:00 - 20:30"]
+</script>
+
+ <a onmouseover="stm(Text[30],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849499&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 20:30 -
+21:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[31]=["Netwerk","20:30 - 21:00<br>Actualiteiten van EO, KRO en NCRV."]
+</script>
+
+ <a onmouseover="stm(Text[31],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849500&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Netwerk</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 21:00 -
+22:35 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[32]=["KRO Detectives: Judge John Deed","21:00 - 22:35<br>Misdaadserie Een jonge vrouw wordt bruut doodgeslagen. Gary Patterson bekent de moord. Hij heeft echter de mentale leeftijd van een jonge tiener en als hij zijn bekentenis ook nog intrekt, wordt het moeilijk de waarheid te achterhalen. Intussen onderzoekt Deed ook een zaak van grootschalige hypotheekfraude. Hierbij blijken procureurs betrokken. Met zijn hardnekkige onderzoek lijk [...]"]
+</script>
+
+ <a onmouseover="stm(Text[32],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849501&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>KRO Detectives: Judge John Deed</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#106F10">
+<div align="center"><font color="#FFFFFF"><b> 22:35 -
+23:05 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[33]=["Dochters + moeders","22:35 - 23:05<br>Ze heeft je gebaard, ze is lief, af en toe wil je haar het liefst achter het behang plakken maar het blijft toch je moeder. Ze is eigenwijs, komt altijd met de foute vriendjes thuis en luistert nooit maar het blijft je dochter. Is er een uniekere band dan die tussen dochter en moeder?."]
+</script>
+
+ <a onmouseover="stm(Text[33],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849502&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Dochters + moeders</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:05 -
+23:32 </b></font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[34]=["De God van Nederland","23:05 - 23:32<br>8-delige Serie reportages waarin de barometerstand van de wonderbaarlijke terugkeer van de christelijke God in de Hollandse polder gepeild wordt en die op zoek gaat naar het Nieuw Religieus Peil. Een serie over verlangen naar idealen, geborgenheid en gemeenschap en een zinvol bestaan als rituele dagsluiting voor gelovigen en ongelovigen, zoekers en zieners, dolende zielen en vast [...]"]
+</script>
+
+ <a onmouseover="stm(Text[34],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849503&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De God van Nederland</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:32 -
+23:40 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[35]=["Wilde Ganzen","23:32 - 23:40"]
+</script>
+
+ <a onmouseover="stm(Text[35],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849504&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wilde Ganzen</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:40 -
+00:05 </b></font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[36]=["Man beet hond","23:40 - 00:05"]
+</script>
+
+ <a onmouseover="stm(Text[36],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849505&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 00:05 -
+06:45 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[37]=["Nacht-tv: Netwerk herhaling","00:05 - 06:45"]
+</script>
+
+ <a onmouseover="stm(Text[37],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849506&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></td>
+</tr>
+</table>
+
+<br />
+
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td rowspan="3"> </td>
+<td style="width: 140px; vertical-align: bottom;"><img
+src="../images/legend_green.gif" /> Currently
+showing<br />
+</td>
+<td style="width: 140px; vertical-align: bottom;"><img
+src="../images/legend_orange.gif" /> Favorite<br />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: middle;"><img
+src="../images/legend_blue.gif" /> Future showing<br />
+</td>
+<td style="vertical-align: middle;"><img
+src="../images/legend_red.gif" /> Scheduled
+recording<br />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: middle;"><img
+src="../images/legend_purple.gif" /> Movie<br />
+</td>
+<td style="vertical-align: middle;"> </td>
+</tr>
+</table>
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=2&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/back.gif" alt="Back" border="0"
+align="middle" /></a><a href="tvstart.php"><img
+src="../images/home.gif" alt="Home" border="0"
+align="middle" /></a><a href="../logout.php"><img
+src="../images/logout.gif" alt="Logout" border="0"
+align="middle" /></a> 22:38, Wednesday 23rd August</td>
+</tr>
+</table>
+</body>
+</html>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name="" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> Monday 21st </time></action>
+
+ <action name="NCRV Dokument: Het recht om te sterven" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787547&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 23:00 -23:45 </time></action>
+
+ <action name="Apocalyps Vietnam" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787548&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 23:45 -00:40 </time></action>
+
+ <action name="Man beet hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787549&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 00:40 -01:10 </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787550&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 01:10 -06:45 </time></action>
+
+ <action name="" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> Monday 21st </time></action>
+
+ <action name="" type="program-info" reference=""><time> 23:13, Monday 21st August</time></action>
+
+ </channel-right-now>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<meta name="generator" content="Adobe GoLive" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="right" />
+
+<h1>Nederland 1</h1>
+
+<table class="tvlist" align="center" width="100%" border="0"
+cellspacing="0" cellpadding="1">
+<tr height="25">
+<td align="left" bgcolor="black"><font
+color="white"><b> Monday 21st </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright_small.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 22:30 -
+00:15 </font></div>
+</td>
+<td align="left" class="listCell2"><a class="movie"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777728676&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>KRO Filmtheater: Hollywood ending</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:15 -
+06:45 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777728677&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Tekst tv</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 06:45 -
+07:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787518&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 07:00 -
+09:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787519&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:00 -
+09:10 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787520&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:10 -
+09:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787521&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:30 -
+10:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787522&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 10:00 -
+11:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787523&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>TROS Muziekfeest in de ArenA 2004</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 11:00 -
+11:25 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787524&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Hello Goodbye</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 11:25 -
+12:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787525&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wat Zou JIJ Doen?</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:00 -
+12:10 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787526&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 12:10 -
+12:40 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787527&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Taxi</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:40 -
+13:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787528&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Lingo</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:00 -
+13:10 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787529&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 13:10 -
+13:20 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787530&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Sportjournaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:20 -
+14:05 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787531&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Warzone: Zuid-Afrika</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 14:05 -
+15:20 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787532&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Tien maal Mozart</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 15:20 -
+16:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787533&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Kruispunt</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 16:00 -
+16:05 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787534&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 16:05 -
+17:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787535&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Oppassen & wegwezen</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:00 -
+17:05 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787536&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 17:05 -
+17:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787537&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Schepper & co</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:30 -
+18:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787538&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Himalaya with Michael Palin</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 18:30 -
+18:55 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787539&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 18:55 -
+19:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787540&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 19:30 -
+20:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787541&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Ingang Oost</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 20:00 -
+20:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787542&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 20:30 -
+21:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787543&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Netwerk</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 21:00 -
+22:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787544&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Memories Tour d'Amour</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 22:00 -
+22:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787545&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Hello Goodbye</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 22:30 -
+23:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787546&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Villa historica</b></a></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#106F10">
+<div align="center"><font color="#FFFFFF"><b> 23:00 -
+23:45 </b></font></div>
+</td>
+<td align="left" bgcolor="#106F10"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787547&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NCRV Dokument: Het recht om te sterven</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:45 -
+00:40 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787548&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Apocalyps Vietnam</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 00:40 -
+01:10 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787549&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 01:10 -
+06:45 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787550&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></b></font></td>
+</tr>
+
+<tr height="25">
+<td align="left" bgcolor="black"><font
+color="white"><b> Monday 21st </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright_small.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+</table>
+
+<table align="center" width="100%" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><br />
+<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=2&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/back_small.gif" alt="Back" border="0"
+align="center" /></a> <a href="tvstart.php"><img
+src="../images/home_small.gif" alt="Home" border="0"
+align="center" /></a> <a href="../logout.php"><img
+src="../images/logout_small.gif" alt="Logout" border="0"
+align="center" /></a> 23:13, Monday 21st August</td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name=">>" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time> </time></action>
+
+ <action name="Wintertijd" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 23:55 - 00:40 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhalingen" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+00:50 - 06:15 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:45 - 06:59 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:59 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 07:00 - 07:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:10 - 07:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:30 - 07:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:40 - 08:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:00 - 08:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:10 - 08:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 08:30 - 08:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:40 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:00 - 09:10 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:10 - 09:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:30 - 09:55 - </time></action>
+
+ <action name="Schoondochter gezocht" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:55 - 10:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 10:50 - 11:15 - </time></action>
+
+ <action name="Appeltje voor de dorst" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+11:15 - 12:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:00 - 12:10 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:10 - 12:35 - </time></action>
+
+ <action name="Voor alle fans: Drukwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:35 - 12:57 - </time></action>
+
+ <action name="Trekking Lingo" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:57 - 13:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:00 - 13:10 - </time></action>
+
+ <action name="NOS-Sportjournaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:10 - 13:20 - </time></action>
+
+ <action name="Buitenhof" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:20 - 14:15 - </time></action>
+
+ <action name="Hoge bomen in de misdaad" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:15 - 14:55 - </time></action>
+
+ <action name="AVRO Dierenpark" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:55 - 15:20 - </time></action>
+
+ <action name="Kruispunt" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>15:20 - 16:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:00 - 16:05 - </time></action>
+
+ <action name="Helden van nu: Vrijwilligers in de gezondheidszorg" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:05 - 16:30 - </time></action>
+
+ <action name="Leven met verlies" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:30 - 17:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:00 - 17:10 - </time></action>
+
+ <action name="Schepper & co" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:10 - 17:35 - </time></action>
+
+ <action name="MAX & Catherine" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:35 - 18:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:30 - 18:55 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:55 - 19:25 - </time></action>
+
+ <action name="Ingang Oost" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>19:25 - 20:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:00 - 20:30 - </time></action>
+
+ <action name="Netwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:30 - 21:05 - </time></action>
+
+ <action name="Memories" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>21:05 - 22:05 - </time></action>
+
+ <action name="Keyzer & De Boer Advocaten" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:05 - 22:55 - </time></action>
+
+ <action name="NCRV Dokument: Een familie van vaders" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:55 - 23:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>23:50 - 00:20 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:20 - 00:50 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:50 - 06:45 - </time></action>
+
+
+BackHomeLogout</channel-right-now>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>Nederland 1</h1>
+
+<b>Monday 13th</b><br />
+<b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+<br />
+ 23:55 - 00:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Wintertijd</a><br />
+00:50 - 06:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nacht-tv: Netwerk herhalingen</a><br />
+06:45 - 06:59 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland in beweging</a><br />
+06:59 - 09:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+ 07:00 - 07:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+07:10 - 07:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+07:30 - 07:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+07:40 - 08:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+08:00 - 08:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+08:10 - 08:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+ 08:30 - 08:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+08:40 - 09:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+09:00 - 09:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+09:10 - 09:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland in beweging</a><br />
+09:30 - 09:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+That's the question</a><br />
+09:55 - 10:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Schoondochter gezocht</a><br />
+ 10:50 - 11:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Blauw bloed</a><br />
+11:15 - 12:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Appeltje voor de dorst</a><br />
+<i>12:00 - 12:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>12:10 - 12:35 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>12:35 - 12:57 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Voor alle fans: Drukwerk</a></i><br />
+<i>12:57 - 13:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Trekking Lingo</a></i><br />
+ <i>13:00 - 13:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>13:10 - 13:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Sportjournaal</a></i><br />
+<i>13:20 - 14:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Buitenhof</a></i><br />
+<i>14:15 - 14:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Hoge bomen in de misdaad</a></i><br />
+<i>14:55 - 15:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+AVRO Dierenpark</a></i><br />
+<i>15:20 - 16:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Kruispunt</a></i><br />
+ <i>16:00 - 16:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>16:05 - 16:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Helden van nu: Vrijwilligers in de gezondheidszorg</a></i><br />
+<i>16:30 - 17:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Leven met verlies</a></i><br />
+<i>17:00 - 17:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>17:10 - 17:35 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Schepper & co</a></i><br />
+ <i>17:35 - 18:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+MAX & Catherine</a></i><br />
+<i>18:30 - 18:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+That's the question</a></i><br />
+<i>18:55 - 19:25 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>19:25 - 20:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Ingang Oost</a></i><br />
+<i>20:00 - 20:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+ <i>20:30 - 21:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Netwerk</a></i><br />
+<i>21:05 - 22:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Memories</a></i><br />
+<i>22:05 - 22:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Keyzer & De Boer Advocaten</a></i><br />
+<i>22:55 - 23:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NCRV Dokument: Een familie van vaders</a></i><br />
+<i>23:50 - 00:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Blauw bloed</a></i><br />
+ <i>00:20 - 00:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>00:50 - 06:45 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nacht-tv: Netwerk herhaling</a></i><br />
+<br />
+ <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=1&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:04, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<favorite-channels>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 1" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 2" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)1~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 3" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)2~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Net5" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)3~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL4" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)4~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="SBS6" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)5~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL5" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)6~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Discovery Channel"
+ type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)7~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL7" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)8~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Veronica" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)9~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="MTV" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)10~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="TheBox" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)11~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Eurosport" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)12~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="CNN" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)13~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="BBC1" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)14~back)$tvhtml$tvstart.php(tz)2"
+ />
+</favorite-channels>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org"/>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1"/>
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css"/>
+ <link rel="shortcut icon" href="../favicon.ico"/>
+ <title>KiSS - Favorite Channels</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <h1>
+ <img src="../images/KiSS_Logo.gif" align="right"/>
+ <a href="tvstart.php">
+ <img border="0" src="../images/tvguide_logo.gif"/>
+ </a>
+ </h1>
+
+ <br/>
+
+
+ <table align="center" width="400" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td align="left">
+ <h1>Favorite Channels</h1>
+
+ <p/>
+ </td>
+ </tr>
+ </table>
+
+ <table align="center" style="width: 400; text-align: left;" border="0" cellpadding="0"
+ cellspacing="0">
+ <tbody>
+ <tr>
+ <td style="vertical-align: bottom; width: 400px;">
+ <img src="../images/tabs_none.gif" alt="" usemap="#tabs_Map" border="0"/>
+ <map id="tabs_Map" name="tabs_Map">
+ <area shape="poly" alt="What's On Now?" coords="126,30, 118,0, 0,0, 0,30"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?reload=1&mode=3&station=0&view=0&now=1"
+ class="grid"/>
+ <area shape="poly" alt="Favorite Shows" coords="239,0, 247,30, 128,30, 120,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ <area shape="poly" alt="Movies" coords="241,0, 250,30, 309,30, 300,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ <area shape="poly" alt="Sports" coords="302,0, 311,30, 370,30, 362,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ </map>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table class="tv" align="center" width="400" border="0" cellspacing="1" cellpadding="1">
+ <tr height="25">
+ <td bgcolor="black" align="right" colspan="3">
+ <font color="white"> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=1&tz=2&back=$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowleft.gif" alt="Prev" border="0" align="texttop"/>
+ </a> <b>Page 1/2</b> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=1&tz=2&back=$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowright.gif" alt="Next" border="0" align="texttop"/>
+ </a> <br/>
+ </font>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/6_h30.gif" align="middle"
+ /> <b>NL1</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 1</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/2_h30.gif" align="middle"
+ /> <b>NL2</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 2</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/5_h30.gif" align="middle"
+ /> <b>NL3</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 3</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/4_h30.gif" align="middle"
+ /> <b>NET5</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <b>Net5</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/9_h30.gif" align="middle"
+ /> <b>RTL4</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL4</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/7_h30.gif" align="middle"
+ /> <b>SBS6</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <b>SBS6</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/3_h30.gif" align="middle"
+ /> <b>RTL5</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL5</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/99_h30.gif" align="middle"
+ /> <b>DISC</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <b>Discovery Channel</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/8_h30.gif" align="middle"
+ /> <b>RTL7</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL7</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/1_h30.gif" align="middle"
+ /> <b>VERO</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <b>Veronica</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/177_h30.gif" align="middle"
+ /> <b>MTV</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <b>MTV</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/665_h30.gif" align="middle"
+ /> <b>BOX</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <b>TheBox</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/94_h30.gif" align="middle"
+ /> <b>ESPO</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <b>Eurosport</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/470_h30.gif" align="middle"
+ /> <b>CNN</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <b>CNN</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/184_h30.gif" align="middle"
+ /> <b>BBC1</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <b>BBC1</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+ </table>
+
+ <table align="center" width="400" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td><br/>
+ <br/>
+ <a href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=2">
+ <img src="../images/back.gif" alt="Back" border="0" align="middle"/>
+ </a><a href="tvstart.php">
+ <img src="../images/home.gif" alt="Home" border="0" align="middle"/>
+ </a><a href="../logout.php">
+ <img src="../images/logout.gif" alt="Logout" border="0" align="middle"/>
+ </a> 17:15, Wednesday 23rd August</td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<favorite-channels> << >><action name="Nederland 1"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Nederland 2"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Nederland 3"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Net5"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL4"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="SBS6"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL5"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)6~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Discovery Channel"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)7~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL7"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Veronica"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Eurosport"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="MTV"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="TheBox"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="CNN"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)13~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="BBC1"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)14~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="BBC2"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=185&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)15~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/> << >>BackHomeLogout</favorite-channels>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Favorite Channels</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>Favorite Channels</h1>
+
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+<<</a> <b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+<br />
+ <b>NL1</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland 1</a><br />
+<b>NL2</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Nederland 2</a><br />
+<b>NL3</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1">
+Nederland 3</a><br />
+ <b>NET5</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1">
+Net5</a><br />
+<b>RTL4</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1">
+RTL4</a><br />
+<b>SBS6</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1">
+SBS6</a><br />
+<b>RTL5</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)6~back)$tvhtml$tvstart.php(tz)1">
+RTL5</a><br />
+ <b>DISC</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)7~back)$tvhtml$tvstart.php(tz)1">
+Discovery Channel</a><br />
+<b>RTL7</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1">
+RTL7</a><br />
+<b>VERO</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1">
+Veronica</a><br />
+<b>ESPO</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1">
+Eurosport</a><br />
+ <b>MTV</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1">
+MTV</a><br />
+<b>BOX</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1">
+TheBox</a><br />
+<b>CNN</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)13~back)$tvhtml$tvstart.php(tz)1">
+CNN</a><br />
+<b>BBC1</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)14~back)$tvhtml$tvstart.php(tz)1">
+BBC1</a><br />
+ <b>BBC2</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=185&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)15~back)$tvhtml$tvstart.php(tz)1">
+BBC2</a><br />
+ <br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+<<</a> <b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=1">Back</a> ] [ <a
+ href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:08, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Favorite Channels</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1> Favorite Channels</h1>
+
+<h2>What's on?</h2>
+
+<a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Right now</a> - 11:43, Monday 13th March<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=20&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Evening</a> - Starting 20:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=16&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1">
+Afternoon</a> - Starting 16:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=12&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1">
+Noon</a> - Starting 12:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=6&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1">
+Morning</a> - Starting 6:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=36&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1">
+Tomorrow</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=60&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Wednesday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=84&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Thursday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=108&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Friday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=132&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Saturday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=156&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Sunday</a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=1">Back</a> ] [ <a
+ href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 11:43, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+ <head>\r
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />\r
+ <meta http-equiv="content-type"\r
+ content="text/html;charset=iso-8859-1" />\r
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css" />\r
+ <link rel='shortcut icon' href='../favicon.ico' />\r
+ <title>KiSS - TV Guide</title>\r
+ </head>\r
+ <body bgcolor="#ffffff">\r
+ <img src="../images/KiSS_Logo.gif" align="right" /><br />\r
+ <br />\r
+ <br />\r
+ \r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td align="left"><img src="../images/tvguide_logo.gif" /><br />\r
+ <br />\r
+ <br />\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td><b>Welcome</b> <u>sf2np2ln9no1</u> /\r
+ <u>web@brakkee.org</u> [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2">\r
+ Change</a> email ]<br />\r
+ <br />\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table class="tvstart" width="400" border="0" cellspacing="0"\r
+ cellpadding="1" align="center">\r
+ <tr>\r
+ <td align="center" height="25" bgcolor="black">\r
+ <div align="center"><font color="white"><b> Favorite\r
+ Channels </b></font></div>\r
+ </td>\r
+ <td align="center" bgcolor="black" width="10"> </td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font color="white"><b> Favorite\r
+ Shows </b></font></div>\r
+ </td>\r
+ <td align="center" bgcolor="black" width="10"> </td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font\r
+ color="white"><b> Movies </b></font></div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="10">\r
+ <td align="center" height="10"></td>\r
+ <td align="center" width="10" height="10"></td>\r
+ <td align="center" height="10"></td>\r
+ <td align="center" width="10" height="10"></td>\r
+ <td align="center" height="10"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on now?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Search a show</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center"></td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Favorites</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Favorites</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font\r
+ color="white"><b> Sports </b></font></div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center"></td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Add a favorite</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td>\r
+ <p><br />\r
+ Recordings to be sent to the player: <b>0</b> [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2">\r
+ View</a> ] [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2">Manual\r
+ recording</a> ]<br />\r
+ <br />\r
+ </p>\r
+ \r
+ <hr />\r
+ <br />\r
+ <b>Time change:</b> In order to see the correct time in the KiSS TV\r
+ Guide, do the following:\r
+ \r
+ <ol>\r
+ <li>On your player press SETUP and set your timezone to reflect\r
+ summer time, for example for mainland Western Europe this should\r
+ be: "CEST (GMT+2)", UK and Ireland: "BST (GMT+1)", Finland and\r
+ Eastern Europe: "EEST (GMT+3)".</li>\r
+ \r
+ <li>Access the TV Guide at least once via your player.</li>\r
+ \r
+ <li>The KiSS TV Guide will now display the correct time for your tv\r
+ programs both via your player and through the web.</li>\r
+ </ol>\r
+ \r
+ <br />\r
+ <hr />\r
+ <br />\r
+ KML favorites: <b>0</b> [ <a\r
+ href="http://epg.kml.kiss-technology.com/favorites/favhtml.php?tz=2&back=$tvhtml$tvstart.php(tz)2">\r
+ View</a> ]<br />\r
+ <br />\r
+ <br />\r
+ <a href="../logout.php"><img src="../images/logout_solo.gif"\r
+ alt="logout" border="0" align="middle" /></a> 16:29,\r
+ Wednesday 23rd August</td>\r
+ </tr>\r
+ </table>\r
+ </body>\r
+</html>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<login>
+ <action name=" unknown Change"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2"
+ type=" unknown Change"/>
+ <action name=" unknown What's on now?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on now?"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown Favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Favorites"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown Search a show"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Search a show"/>
+ <action name=" unknown Favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Favorites"/>
+ <action name=" unknown Add a favorite"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Add a favorite"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name="view-recordings"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type="view-recordings"/>
+ <action name="manual-recording"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2"
+ type="manual-recording"/>
+ <action name=" unknown "
+ reference="../logout.php"
+ type=" unknown "/>
+</login>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+
+<h1>TV Guide</h1>
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><b>Welcome</b> <u>sf2np2ln9no1</u> /
+<u>web@brakkee.org</u> [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2">
+Change</a> email ]<br />
+<br />
+ </td>
+</tr>
+</table>
+
+<table class="tvstart" width="100%" border="0" cellspacing="0"
+cellpadding="1" align="center">
+<tr>
+<td align="center" height="25" bgcolor="black">
+<div align="center"><font color="white"><b> Favorite
+Channels </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on now?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Favorites</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font color="white"><b> Favorite
+Shows </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Search a show</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Favorites</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Add a favorite</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font
+color="white"><b> Movies </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font
+color="white"><b> Sports </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center"></td>
+</tr>
+</table>
+
+<table align="center" width="100%" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td>
+<p><br />
+Recordings to be sent to the player: 0 [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2">
+View</a> ] [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2">Manual
+recording</a> ]<br />
+<br />
+ <a href="../logout.php"><img src="../images/logout_small.gif"
+alt="logout" border="0" align="center" /></a> 22:19, Monday
+21st August</p>
+</td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<login>
+ <action
+ name=" unknown Change"
+ type=" unknown Change"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=1"/>
+ <action name="channels-whats-on-now" type="channels-whats-on-now"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="channels-whats-on" type="channels-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="channels-favorites" type="channels-favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-whats-on" type="shows-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-search" type="shows-search"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-favorites" type="shows-favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-add-favorite" type="shows-add-favorite"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="movies-whats-on" type="movies-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="sports-whats-on" type="sports-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="view-recordings" type="view-recordings"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="manual-recording" type="manual-recording"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=1"/>
+ <action name="logout" type="logout" reference="../logout.php"/>
+</login>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />
+ <meta http-equiv="content-type"
+ content="text/html;charset=iso-8859-1" />
+ <title>KiSS - TV Guide</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <img src="../images/KiSS_Logo_small.gif" align="center" />
+
+ <h1>TV Guide</h1>
+
+ <b>Welcome</b> <u>sf2np2ln9no1</u> /
+ <u>erik@brakkee.org</u> [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=1">
+ Change</a> email ]<br />
+ <br />
+
+
+ <h2>Favorite Channels</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on now?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Favorites</a>
+
+ <h2>Favorite Shows</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Search a show</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Favorites</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Add a favorite</a>
+
+ <h2>Movies</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a>
+
+ <h2>Sports</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a>
+
+ <p><br />
+ Recordings to be sent to the player: 0<br />
+ [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ View</a> ] [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=1">Manual
+ recording</a> ]<br />
+ <br />
+ [ <a href="../logout.php">Logout</a> ]<br />
+ 21:09, Sunday 12th March</p>
+ </body>
+ </html>
+
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />
+ <meta content="text/html;charset=iso-8859-1"
+ http-equiv="content-type" />
+ <link href="kiss.css" type="text/css" rel="STYLESHEET" />
+ <link rel='shortcut icon' href='favicon.ico' />
+ <title>KiSS Technology Online Portal</title>
+ <script type="text/javascript" language="JavaScript">
+ //<![CDATA[
+ function placeFocus() {
+ if (document.forms.length > 0) {
+ var field = document.forms[0];
+ for (i = 0; i < field.length; i++) {
+ if ((field.elements[i].type == "text") || (field.elements[i].type == "textarea") || (field.elements[i].type.toString().charAt(0) == "s")) {
+ document.forms[0].elements[i].focus();
+ break; } } } }
+ function placeFocusP() {
+ if (document.forms.length > 0) {
+ var field = document.forms[0];
+ for (i = 0; i < field.length; i++) {
+ if ((field.elements[i].type == "password")) {
+ document.forms[0].elements[i].focus();
+ break; } } } }
+ //]]>
+ </script>
+ </head>
+ <body bgcolor="#ffffff" onload="placeFocus()">
+ <div align="center">
+ <h2><img src="images/kiss_logo_login.gif"
+ align="middle" /> Web Services:<br />
+ <img src="images/tvguide_logo.gif" align="middle" /><br />
+ </h2>
+ </div>
+
+ <table align="center" border="0" cellpadding="0" cellspacing="0"
+ style="width: 240px;">
+ <tbody>
+ <tr>
+ <td style="text-align: left;">
+ <h2> Login</h2>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <br />
+
+
+ <form name="FormName" method="post" action="login_core.php"
+ id="FormName"><input type="hidden" name="token"
+ value="e1c6b500600a0a2ba585ae52338a817f" />
+
+ <table align="center" border="0" cellpadding="0" cellspacing="0"
+ background="images/login_background.gif"
+ style="width: 240px; height: 240px;">
+ <tbody>
+ <tr>
+ <td style="vertical-align: top;"><br />
+ </td>
+ <td style="vertical-align: top;"><br />
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align: right; width: 100px;">Player ID <br />
+ or Email: </td>
+ <td style="width: 180px;"><input type="text" maxlength="50"
+ size="20" name="user" value="" /> *<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align: right; width: 100px;">Password: </td>
+ <td><input type="password" maxlength="10" size="8" name="passwd" />
+ **<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="checkbox" value="1" name="SavePlayerID" /> Save
+ PlayerID ***<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="radio" checked="checked" value="GraphicMode"
+ name="GMode" />Desktop mode<br />
+ <input type="radio" value="MobileMode" name="GMode" />Mobile
+ mode<br />
+ <input type="radio" value="TextMode" name="GMode" />Text mode</td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="submit" value="Login" name="submit" /></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><br />
+ </td>
+ <td style="vertical-align: top;"><br />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </form>
+
+ <center>[ <a href="l.php">Text version</a> ]</center>
+
+ <br />
+
+
+ <table align="center" style="text-align: center; width: 600px;"
+ border="0" cellspacing="2" cellpadding="2">
+ <tbody>
+ <tr>
+ <td style="vertical-align: top;"><font size="1">* You can find your
+ player id by pressing Menu on your remote control > Online
+ KML Services > Reveal PlayerID.<br />
+ You can use your email address instead of player id only when you
+ have already logged in once and associated your email address with
+ your player id.</font></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><font size="1">** You must have
+ already configured your password by going to the EPG start page
+ > Configure > Set Password</font></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><font size="1">*** Saving player
+ id / email requires cookies</font></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <br />
+ </body>
+ </html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><program-info>IMDbWhen is it on?<title> KRO Filmtheater: Hollywood ending </title><keywords> Film </keywords><description>Komisch filmdrama De regisseur Val Waxman was ooit erg succesvol.
+ Tegenwoordig regisseert hij echter alleen nog maar tv-commercials. Eindelijk krijgt hij weer
+ eens een aanbod om een grote film te maken. Het lot wil echter dat Val op dat moment tijdelijk
+ blind wordt, als resultaat van zijn paranoia. Hij probeert samen met enkele vrienden op de set
+ zijn handicap te verbergen.</description></program-info>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org"/>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1"/>
+ <meta name="generator" content="Adobe GoLive"/>
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css"/>
+ <link rel="shortcut icon" href="../favicon.ico"/>
+ <title>KiSS - Program info</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <img src="../images/KiSS_Logo_small.gif" align="center"/>
+
+ <h1> Program info</h1>
+
+ <table class="tvinfo" align="center" border="0" cellspacing="0" cellpadding="1" width="100%">
+ <tr>
+ <td colspan="3" align="left" bgcolor="black" width="50%">
+ <font size="+1" color="white">
+ <b> KRO Filmtheater: Hollywood ending </b>
+ </font>
+ </td>
+ <td width="10" bgcolor="black"/>
+ <td colspan="3" align="right" bgcolor="black">
+ <font size="+1" color="white">
+ <b> Nederland 1 <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1777728675&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowleft_small.gif" alt="Prev" border="0" align="middle"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1777728677&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowright_small.gif" alt="Next" border="0" align="middle"/>
+ </a> </b>
+ </font>
+ </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td colspan="3" align="center" bgcolor="#B3C5D9" width="50%">
+ <font color="#000000">Sunday 20th August</font>
+ </td>
+ <td width="10"/>
+ <td class="listCell1" colspan="3" align="center" bgcolor="#cccccc">
+ Film </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td align="center" bgcolor="#B3C5D9">
+ <font color="#000000">22:30 - 00:15</font>
+ </td>
+ <td width="10"/>
+ <td class="listCell1" align="center" bgcolor="#cccccc">(01:45 hours)</td>
+ <td width="10"/>
+ <td class="listCell1" colspan="3" align="center">
+ <div align="center"> </div>
+ </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td colspan="7" align="left">Komisch filmdrama De regisseur Val Waxman was ooit erg succesvol.
+ Tegenwoordig regisseert hij echter alleen nog maar tv-commercials. Eindelijk krijgt hij weer
+ eens een aanbod om een grote film te maken. Het lot wil echter dat Val op dat moment tijdelijk
+ blind wordt, als resultaat van zijn paranoia. Hij probeert samen met enkele vrienden op de set
+ zijn handicap te verbergen.</td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td class="listCell1" colspan="7" align="left" valign="middle"> [ <a target="_blank"
+ href="http://www.imdb.com/find?q=KRO+Filmtheater%3A+Hollywood+ending" class="grid"
+ >IMDb</a> ] [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch_show.php?tz=2&tvshow=KRO+Filmtheater%3A+Hollywood+ending&back=$tvhtml$tvinfo.php(tz)2~id)1777728676~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"
+ class="grid">When is it on?</a> ] </td>
+ </tr>
+ </table>
+
+ <table align="center" width="100%" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td><br/>
+ <br/>
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&day=0&page=0&tz=2&progid=0&sel=0&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/back_small.gif" alt="Back" border="0" align="center"/>
+ </a> <a href="tvstart.php">
+ <img src="../images/home_small.gif" alt="Home" border="0" align="center"/>
+ </a> <a href="../logout.php">
+ <img src="../images/logout_small.gif" alt="Logout" border="0" align="center"/>
+ </a> 23:50, Monday 21st August</td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><program-info>
+<<
+>>IMDb<action name="record" reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_add.php?id=1772395857&tz=1&back=$tvhtml$tvinfo.php(station)6~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1" type="recorded">
+
+ </action>When is it on?
+BackHomeLogout<title>Kruispunt</title><keywords>
+ Religieus</keywords><description>
+
+ Achtergronden uit kerk en samenleving.
+
+</description></program-info>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Program info</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1> Program info</h1>
+
+<h2>Kruispunt</h2>
+
+<h2>Nederland 1 <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+<<</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+>></a></h2>
+
+<b>Monday 13th March</b><br />
+ Religieus<br />
+ <b>15:20 - 16:00</b> (40 minutes)<br />
+ <br />
+ Achtergronden uit kerk en samenleving.<br />
+ <br />
+<br />
+[ <a target="_blank"
+href="http://www.imdb.com/find?q=Kruispunt"
+class="grid">IMDb</a> ] [ <img
+src="../images/record_dot.gif" align="middle" hspace="2" /><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_add.php?id=1772395857&tz=1&back=$tvhtml$tvinfo.php(station)6~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Record</a> ] [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch_show.php?tz=1&tvshow=Kruispunt&back=$tvhtml$tvinfo.php(tz)1~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">When is it on?</a> ] <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&day=0&page=0&tz=1&progid=0&sel=0&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:16, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<link rel="STYLESHEET" type="text/css"
+href="http://epg.kml.kiss-technology.com/kiss.css" />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<h1><img
+src="http://epg.kml.kiss-technology.com/images/KiSS_Logo.gif"
+align="middle" />Error</h1>
+
+Show is already in the recording queue!<br />
+<br />
+[ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=6&id=1772583278&back=$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ]<br />
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<link rel="STYLESHEET" type="text/css"
+href="http://epg.kml.kiss-technology.com/kiss.css" />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<h1><img
+src="http://epg.kml.kiss-technology.com/images/KiSS_Logo.gif"
+align="middle" />Error</h1>
+
+This show conflicts with a recording that is already in the <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvinfo.php(station)2~id)1772583335~back)$tvhtml$tvlist.php(channel)2~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)2~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+recording queue</a>!<br />
+<br />
+[ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=2&id=1772583335&back=$tvhtml$tvlist.php(channel)2~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)2~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ]<br />
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - TV Guide - Recordings</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>TV Guide - Recordings</h1>
+
+<h2>Recordings already sent to player</h2>
+
+<b>SBS6</b> - Lois & Clark: The new adventures of Superman -
+ 08:00 - 09:00 -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541086&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <b>VERO</b> - Brainiac -
+<i> 19:40 - 20:10 </i> -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541060&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+<b>VERO</b> - Brainiac - <i> 19:40 - 20:10 </i>
+- <i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541060&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+ <b>DISC</b> - Brainiac -
+<i> 22:00 - 23:00 </i> -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772540925&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+<b>DISC</b> - Brainiac - <i> 22:00 - 23:00 </i>
+- <i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772540925&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+ <b>NL3</b> - The Kumars at no. 42 -
+<i> 15:55 - 16:26 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583485&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+<b>VERO</b> - Stargate SG-1 -
+<i> 18:10 - 18:55 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583562&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <b>VERO</b> - Battlestar Galactica -
+<i> 19:35 - 20:25 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583564&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <br />
+
+<h2>Recordings to be sent to player</h2>
+
+<b>NL1</b> - Samen tegen Kanker -
+<i> 20:30 - 22:25 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583278&from=1&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=6&id=1772583278&back=$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 19:46, Friday 17th March
+</body>
+</html>
+
--- /dev/null
+<report>
+
+ <messages>
+ <message>Hello world!</message>
+ <message>and another message</message>
+ </messages>
+
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent.</description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<!-- dependencies of the kiss crawler itself -->
+
+<target name="kisscrawler.src.d"
+ depends="logging.d,mail.d,commons-email.d,commons-beanutils.d,commons-codec.d,dom4j.d,xerces.d,httpclient.d,jtidy.d,wamblee.support.d,wamblee.crawler.d,spring.d">
+</target>
+
+<target name="kisscrawler.test.d" depends="wamblee.support.test.d,wamblee.crawler.test.d">
+</target>
+
+
+<!-- dependency to use for depending on the kiss crawler -->
+
+<property name="kisscrawler.dist.dir" value="${lib.dir}/wamblee/crawler/kiss"/>
+<target name="wamblee.kisscrawler.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${kisscrawler.dist.dir}">
+ <include name="wamblee-crawler-kiss.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.kisscrawler.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${kisscrawler.dist.dir}">
+ <include name="wamblee-crawler-kiss-test.jar"/>
+ </fileset>
+ </copy>
+</target>
--- /dev/null
+This is the base documentation directory.
+
+skinconf.xml # This file customizes Forrest for your project. In it, you
+ # tell forrest the project name, logo, copyright info, etc
+
+sitemap.xmap # Optional. This sitemap is consulted before all core sitemaps.
+ # See http://forrest.apache.org/docs/project-sitemap.html
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+#=======================================================================
+# CatalogManager.properties for Catalog Entity Resolver.
+#
+# This is the default properties file for your project.
+# This facilitates local configuration of application-specific catalogs.
+# If you have defined any local catalogs, then they will be loaded
+# before Forrest's core catalogs.
+#
+# See the Apache Forrest documentation:
+# http://forrest.apache.org/docs/your-project.html
+# http://forrest.apache.org/docs/validation.html
+
+# verbosity:
+# The level of messages for status/debug (messages go to standard output).
+# The setting here is for your own local catalogs.
+# The verbosity of Forrest's core catalogs is controlled via
+# main/webapp/WEB-INF/cocoon.xconf
+#
+# The following messages are provided ...
+# 0 = none
+# 1 = ? (... not sure yet)
+# 2 = 1+, Loading catalog, Resolved public, Resolved system
+# 3 = 2+, Catalog does not exist, resolvePublic, resolveSystem
+# 10 = 3+, List all catalog entries when loading a catalog
+# (Cocoon also logs the "Resolved public" messages.)
+verbosity=1
+
+# catalogs ... list of additional catalogs to load
+# (Note that Apache Forrest will automatically load its own default catalog
+# from main/webapp/resources/schema/catalog.xcat)
+# Use either full pathnames or relative pathnames.
+# pathname separator is always semi-colon (;) regardless of operating system
+# directory separator is always slash (/) regardless of operating system
+catalogs=../resources/schema/catalog.xcat
+
+# relative-catalogs
+# If false, relative catalog URIs are made absolute with respect to the
+# base URI of the CatalogManager.properties file. This setting only
+# applies to catalog URIs obtained from the catalogs property in the
+# CatalogManager.properties file
+# Example: relative-catalogs=[yes|no]
+relative-catalogs=no
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<html>
+ <head>
+ <title>Raw un-processed HTML page (test1)</title>
+ </head>
+ <body>
+ <h1>raw un-processed HTML page (test1)</h1>
+ <p>
+ This raw HTML page is linked to from xdocs/samples/static.xml
+ and from xdocs/samples/linking.xml
+ </p>
+ <p>All linked-to pages (for example:
+ <a href="test2.html"><a href="test2.html"></a>) are
+ also available.
+ </p>
+ <hr />
+ <p>
+ [return to <a href="index.html">Index</a>]<br>
+ [return to <a href="samples/linking.html">Linking demonstration</a>]
+ </p>
+ </body>
+</html>
--- /dev/null
+<html>
+<head>
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>KiSS crawler report</title>
+</head>
+<body>
+<h1>KiSS crawler report</h1>
+<h2>Successfully recorded programs <p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>18:00 - 18:55: <strong>Stargate SG-1</strong> (Veronica/Serie/soap)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Sf-serie SG-1 krijgt een aanbod van een buitenaardse wereld voor een wondermedicijn.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>19:25 - 20:25: <strong>Stargate SG-1</strong> (Veronica/Serie/soap)</td>
+</tr>
+<tr>
+
+<td>
+<blockquote>
+<font size="-1">Sf-serie Tijdens een vlucht van de nieuwe X-303, codenaam Prometheus, wordt het schip overmand door NID-agenten.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</h2>
+<h2>Conflicts with other recorded programs<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>20:00 - 20:45: <strong>Doctor Who</strong> (BBC1/Drama)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Madame de Pompadour finds the court at Versailles under attack from sinister clockwork killers. Her only hope of salvation lies with the man who has haunted her dreams since childhood - a mysterious stranger known only as the Doctor.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</h2>
+<h2>Possibly interesting programs</h2>
+<p>
+<table cellpadding="5" align="left"></table>
+
+<br clear="left">
+</p>
+<h3>Category: documentaires</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>11:50 - 12:30: <strong>Zembla</strong> (Nederland 3/Documentaire)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1"></font>
+
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>13:10 - 13:35: <strong>Andere tijden</strong> (Nederland 3/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Geschiedenisrubriek met reportages over (bijna) vergeten gebeurtenissen uit de twintigste eeuw. De redactie gaat op zoek naar ooggetuigen en betrokkenen en naar historische filmbeelden om aan de hand daarvan de verhalen van vroeger opnieuw te vertellen.</font>
+</blockquote>
+</td>
+
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: films</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>13:10 - 13:35: <strong>Andere tijden</strong> (Nederland 3/Film)</td>
+</tr>
+<tr>
+<td>
+
+<blockquote>
+<font size="-1">Geschiedenisrubriek met reportages over (bijna) vergeten gebeurtenissen uit de twintigste eeuw. De redactie gaat op zoek naar ooggetuigen en betrokkenen en naar historische filmbeelden om aan de hand daarvan de verhalen van vroeger opnieuw te vertellen.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>22:00 - 06:00: <strong>Face off</strong> (Veronica/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Actiefilm Een FBI-agent wil kost wat het kost een psychotische terrorist pakken, die verantwoordelijk is voor de moord op zijn zoontje. In de strijd om de terrorist in te rekenen raakt deze in een coma. Hij heeft de agent echter nog net kunnen vertellen dat ergens in Los Angeles een bom verborgen ligt. Om op het spoor van deze bom te komen vragen regeringsfunctionarissen of de FBI-agent zich uit wil geven als de moordenaar van zijn zoon. Door middel van een medische ingreep worden de gezichten van beiden verwisseld met alle gevolgen van dien.</font>
+
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>23:25 - 01:00: <strong>Gossip</strong> (Net5/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Thriller Drie studenten doen voor een schoolproject een proef over roddelen. Ze verspreiden een gerucht om te zien hoe lang het duurt voordat deze zich heeft verspreid. Maar wat begint als een onschuldige roddel, escaleert tot een groot misverstand en leidt zelfs tot een arrestatie wegens verkrachting. Het drietal beseft dat hun vooropgezete plan desastreuze gevolgen heeft en dat hun experiment niet meer te stoppen is.</font>
+</blockquote>
+</td>
+
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: science fiction</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>08:00 - 09:00: <strong>Lois & Clark: The new adventures of Superman</strong> (SBS6/Serie/soap)</td>
+</tr>
+
+<tr>
+<td>
+<blockquote>
+<font size="-1">Sf-serie Lex Luthor ontwikkelt verschillende tests om de kracht van Superman te doorgronden.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: wetenschap</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>11:30 - 12:25: <strong>Triumph of life</strong> (RTL4/Documentaire)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Serie documentaires over de evolutie. Al het leven op aarde is ooit ontstaan uit een organisme dat zich door omstandigheden heeft kunnen ontwikkelen tot een wezen dat zich wist voort te planten. Dit ingewikkelde proces voltrok zich miljarden jaren geleden en is sindsdien gaande. Het werd in de negentiende eeuw voor het eerst in kaart gebracht door de Britse bioloog Charles Darwin. Sindsdien wordt de evolutietheorie wetenschappelijk onderzocht en betwijfeld, maar het feit is dat het leven zich in diverse vormen blijft ontwikkelen.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:00 - 00:30: <strong>Sex sense: Bi way</strong> (Discovery Channel/Wetenschap)</td>
+</tr>
+<tr>
+
+<td>
+<blockquote>
+<font size="-1">Documentaireserie Onderzoek naar de wetenschap van seksualiteit, gecombineerd met levendige beelden en ondeugende humor.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:30 - 01:00: <strong>Sex sense: Baring it all</strong> (Discovery Channel/Wetenschap)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+
+<font size="-1">Documentaireserie Onderzoek naar de wetenschap van seksualiteit, gecombineerd met levendige beelden en ondeugende humor.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:40 - 02:10: <strong>Top secret!</strong> (SBS6/Comedy)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Filmkomedie De knappe jaren '50 rock-'n-roll-ster Nick Rivers is in Oost-Duitsland om op te treden. Daar wordt hij verliefd op de dochter van een ontvoerde wetenschapper en komt hij in contact met het Franse verzet.</font>
+
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</body>
+</html>
--- /dev/null
+%PDF-1.3
+%ª«¬
+4 0 obj
+<< /Type /Info
+/Producer (FOP 0.20.4) >>
+endobj
+5 0 obj
+<< /Length 203 /Filter [ /ASCII85Decode /FlateDecode ]
+ >>
+stream
+Gar'!]afWZ&;9q-MRA)RFnblL2&]tQSZsjOOT[ck2SQkp(bfQ[R7ZPq=U24c0dqq_i?B[A.0s\)5f5<IA'lb0eeo`C+`q\Ip/Tke*)7%T+.hT8:QQidXoPLKZM,RXY"bP+;E@%,ZX;V'Aq+M9rH"!g=N5TToDMoqMeUiEe).I_W3q80:jF+;'9bVIeBRb]DhE9:E2be2~>
+endstream
+endobj
+6 0 obj
+<< /Type /Page
+/Parent 1 0 R
+/MediaBox [ 0 0 595 842 ]
+/Resources 3 0 R
+/Contents 5 0 R
+>>
+endobj
+7 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F1
+/BaseFont /Helvetica
+/Encoding /WinAnsiEncoding >>
+endobj
+8 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F5
+/BaseFont /Times-Roman
+/Encoding /WinAnsiEncoding >>
+endobj
+1 0 obj
+<< /Type /Pages
+/Count 1
+/Kids [6 0 R ] >>
+endobj
+2 0 obj
+<< /Type /Catalog
+/Pages 1 0 R
+ >>
+endobj
+3 0 obj
+<<
+/Font << /F1 7 0 R /F5 8 0 R >>
+/ProcSet [ /PDF /ImageC /Text ] >>
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000687 00000 n
+0000000745 00000 n
+0000000795 00000 n
+0000000015 00000 n
+0000000071 00000 n
+0000000365 00000 n
+0000000471 00000 n
+0000000578 00000 n
+trailer
+<<
+/Size 9
+/Root 2 0 R
+/Info 4 0 R
+>>
+startxref
+883
+%%EOF
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+ SVG Anteater logo
+
+To get started with SVG, I'd recommend getting the Adobe SVG plugin, and the
+xml-batik CVS module. Then have a look at the xml-batik/samples files. Use the
+SVG spec (http://www.w3.org/TR/SVG/) as a reference.
+-->
+
+<!-- See Forrest Issue: FOR-229
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg xmlns:for CDATA #FIXED "http://apache.org/forrest">
+ <!ENTITY % textExt "|for:group-name">
+ <!ELEMENT for:group-name (#PCDATA)>
+]>
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xsl:version="1.0"
+ xmlns:for="http://apache.org/forrest"
+ width="220" height="65" >
+ <title>Anteater logo</title>
+
+ <defs>
+
+ <!--
+ <radialGradient id="radialGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </linearGradient>
+ -->
+
+ <linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
+ <stop style="stop-color:white" offset="0"/>
+ <stop style="stop-color:lightgreen" offset="1"/>
+ </linearGradient>
+
+ <filter id="shadowFilter" filterUnits="objectBoundingBox" width="1.4" height="1.4">
+ <!-- Takes the alpha channel (black outline of the text), blurs it and saves as 'blur' -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="2 2" result="blur"/>
+ <!-- Takes saved 'blur' and offsets it by 4 pixels, saves as 'offsetBlur' -->
+ <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <!-- Merges SourceGraphic (original image) and 'offsetBlur', putting the
+ former 'over' the latter, and using the merged result as the finished
+ image -->
+ <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/>
+ </filter>
+
+ </defs>
+
+ <g filter="url(#shadowFilter)" fill="url(#gradient)">
+ <text x="40%" y="60%" style="font-size:24pt; font-family:Verdana ; text-anchor: middle">
+ <for:group-name />
+ </text>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+ SVG Anteater logo
+
+To get started with SVG, I'd recommend getting the Adobe SVG plugin, and the
+xml-batik CVS module. Then have a look at the xml-batik/samples files. Use the
+SVG spec (http://www.w3.org/TR/SVG/) as a reference.
+-->
+
+<!-- See Forrest Issue: FOR-229
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg xmlns:for CDATA #FIXED "http://apache.org/forrest">
+ <!ENTITY % textExt "|for:project-name">
+ <!ELEMENT for:project-name (#PCDATA)>
+]>
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xsl:version="1.0"
+ xmlns:for="http://apache.org/forrest"
+ width="420" height="65" >
+ <title>Anteater logo</title>
+
+ <defs>
+
+ <!--
+ <radialGradient id="radialGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </linearGradient>
+ -->
+
+ <linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
+ <stop style="stop-color:white" offset="0"/>
+ <stop style="stop-color:lightgreen" offset="1"/>
+ </linearGradient>
+
+ <filter id="shadowFilter" filterUnits="objectBoundingBox" width="1.4" height="1.4">
+ <!-- Takes the alpha channel (black outline of the text), blurs it and saves as 'blur' -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="2 2" result="blur"/>
+ <!-- Takes saved 'blur' and offsets it by 4 pixels, saves as 'offsetBlur' -->
+ <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <!-- Merges SourceGraphic (original image) and 'offsetBlur', putting the
+ former 'over' the latter, and using the merged result as the finished
+ image -->
+ <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/>
+ </filter>
+
+ </defs>
+
+ <g filter="url(#shadowFilter)" fill="url(#gradient)">
+ <text x="100%" y="60%" style="font-size:24pt; font-family:Verdana ; text-anchor: end" >
+ <for:project-name />
+ </text>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Automatic Recording for KiSS Hard Disk Recorders</title>
+ </header>
+ <body>
+ <warning>
+ KiSS makes regular updates to their site that sometimes require adaptations
+ to the crawler. If it stops working, check out the most recent version here.
+ </warning>
+ <section id="changelog">
+ <title>Changelog</title>
+
+ <section>
+ <title>31 August 2006</title>
+ <ul>
+ <li>Added windows bat file for running the crawler under windows.
+ Very add-hoc, will be generalized. </li>
+ </ul>
+ </section>
+ <section>
+ <title>24 August 2006</title>
+ <ul>
+ <li>The crawler now uses desktop login for crawling. Also, it is much more efficient since
+ it no longer needs to crawl the individual programs. This is because the channel page
+ includes descriptions of programs in javascript popups which can be used by the crawler.
+ The result is a significant reduction of the load on the KiSS EPG site. Also, the delay
+ between requests has been increased to further reduce load on the KiSS EPG site. </li>
+ <li>
+ The crawler now crawls programs for tomorrow instead of for today.
+ </li>
+ <li>
+ The web based crawler is configured to run only between 7pm and 12pm. It used to run at
+ 5am.
+ </li>
+ </ul>
+ </section>
+
+ <section>
+ <title>13-20 August 2006</title>
+ <p>
+ There were several changes to the login procedure, requiring modifications to the crawler.
+ </p>
+ <ul>
+ <li>The crawler now uses the 'Referer' header field correctly at login.</li>
+ <li>KiSS now uses hidden form fields in their login process which are now also handled correctly by the
+ crawler.</li>
+ </ul>
+ </section>
+ </section>
+ <section id="overview">
+ <title>Overview</title>
+
+ <p>
+ In 2005, <a href="site:links/kiss">KiSS</a> introduced the ability
+ to schedule recordings on KiSS hard disk recorder (such as the
+ DP-558) through a web site on the internet. When a new recording is
+ scheduled through the web site, the KiSS recorder finds out about
+ this new recording by polling a server on the internet.
+ This is a really cool feature since it basically allows programming
+ the recorder when away from home.
+ </p>
+ <p>
+ After using this feature for some time now, I started noticing regular
+ patterns. Often you are looking for the same programs and for certain
+ types of programs. So, wouldn't it be nice to have a program
+ do this work for you and automatically record programs and notify you
+ of possibly interesting ones?
+ </p>
+ <p>
+ This is where the KiSS crawler comes in. This is a simple crawler which
+ logs on to the KiSS electronic programme guide web site and gets
+ programme information from there. Then based on that it automatically
+ records programs for you or sends notifications about interesting ones.
+ </p>
+ <p>
+ In its current version, the crawler can be used in two ways:
+ </p>
+ <ul>
+ <li><strong>standalone program</strong>: A standalone program run as a scheduled task.</li>
+ <li><strong>web application</strong>: A web application running on a java
+ application server. With this type of use, the crawler also features an automatic retry
+ mechanism in case of failures, as well as a simple web interface. </li>
+ </ul>
+ </section>
+
+ <section>
+ <title>Downloading</title>
+
+ <p>
+ At this moment, no formal releases have been made and only the latest
+ version can be downloaded.
+ </p>
+ <p>
+ The easy way to start is the
+ <a href="installs/crawler/kiss/kiss-crawler-bin.zip">standalone program binary version</a>
+ or using the <a href="installs/crawler/kissweb/wamblee-crawler-kissweb.war">web
+ application</a>.
+ </p>
+ <p>
+ The latest source can be obtained from subversion with the
+ URL <code>https://wamblee.org/svn/public/utils</code>. The subversion
+ repository allows read-only access to anyone.
+ </p>
+ <p>
+ The application was developed and tested on SuSE linux 9.1 with JBoss 4.0.2 application
+ server (only required for the web application). It requires at least a Java Virtual Machine
+ 1.5 or greater to run.
+ </p>
+ </section>
+
+ <section>
+ <title>Configuring the crawler</title>
+
+ <p>
+ The crawler comes with three configuration files:
+ </p>
+ <ul>
+ <li><code>crawler.xml</code>: basic crawler configuration
+ tailored to the KiSS electronic programme guide.</li>
+ <li><code>programs.xml</code>: containing a description of which
+ programs must be recorded and which programs are interesting.</li>
+ <li><code>org.wamblee.crawler.properties</code>: Containing a configuration </li>
+ </ul>
+ <p>
+ For the standalone program, all configuration files are in the <code>conf</code> directory.
+ For the web application, the properties files is located in the <code>WEB-INF/classes</code>
+ directory of the web application, and <code>crawler.xml</code> and <code>programs.xml</code>
+ are located outside of the web application at a location configured in the properties file.
+ </p>
+
+
+ <section>
+ <title>Crawler configuration <code>crawler.xml</code></title>
+
+ <p>
+ First of all, copy the <code>config.xml.example</code> file
+ to <code>config.xml</code>. After that, edit the first entry of
+ that file and replace <code>user</code> and <code>passwd</code>
+ with your personal user id and password for the KiSS Electronic
+ Programme Guide.
+ </p>
+ </section>
+
+ <section>
+ <title>Program configuration</title>
+ <p>
+ Interesting TV shows are described using <code>program</code>
+ elements. Each <code>program</code> element contains
+ one or more <code>match</code> elements that describe
+ a condition that the interesting program must match.
+ </p>
+ <p>
+ Matching can be done on the following properties of a program:
+ </p>
+ <table>
+ <tr><th>Field name</th>
+ <th>Description</th></tr>
+ <tr>
+ <td>name</td>
+ <td>Program name</td>
+ </tr>
+ <tr>
+ <td>description</td>
+ <td>Program description</td>
+ </tr>
+ <tr>
+ <td>channel</td>
+ <td>Channel name</td>
+ </tr>
+ <tr>
+ <td>keywords</td>
+ <td>Keywords/classification of the program.</td>
+ </tr>
+ </table>
+ <p>
+ The field to match is specified using the <code>field</code>
+ attribute of the <code>match</code> element. If no field name
+ is specified then the program name is matched. Matching is done
+ by converting the field value to lowercase and then doing a
+ perl-like regular expression match of the provided value. As a
+ result, the content of the match element should be specified in
+ lower case otherwise the pattern will never match.
+ If multiple <code>match</code> elements are specified for a
+ given <code>program</code> element, then all matches must
+ apply for a program to be interesting.
+ </p>
+ <p>
+ Example patterns:
+ </p>
+ <table>
+ <tr>
+ <th>Pattern</th>
+ <th>Example of matching field values</th>
+ </tr>
+ <tr>
+ <td>the.*x.*files</td>
+ <td>"The X files", "The X-Files: the making of"</td>
+ </tr>
+ <tr>
+ <td>star trek</td>
+ <td>"Star Trek Voyager", "Star Trek: The next generation"</td>
+ </tr>
+ </table>
+
+ <p>
+ It is possible that different programs cannot be recorded
+ since they overlap. To deal with such conflicts, it is possible
+ to specify a priority using the <code>priority</code> element.
+ Higher values of the priority value mean a higher priority.
+ If two programs have the same priority, then it is (more or less)
+ unspecified which of the two will be recorded, but it will at least
+ record one program. If no priority is specified, then the
+ priority is 1 (one).
+ </p>
+
+ <p>
+ Since it is not always desirable to try to record every
+ program that matches the criteria, it is also possible to
+ generate notifications for interesting programs only without
+ recording them. This is done by specifying the
+ <code>action</code> alement with the content <code>notify</code>.
+ By default, the <code>action</code> is <code>record</code>.
+ To make the mail reports more readable it is possible to
+ also assign a category to a program for grouping interesting
+ programs. This can be done using the <code>category</code>
+ element. Note that if the <code>action</code> is
+ <code>notify</code>. then the <code>priority</code> element
+ is not used.
+ </p>
+
+ </section>
+
+ <section>
+ <title>Notification configuration</title>
+ <p>
+ Edit the configuration file <code>org.wamblee.crawler.properties</code>.
+ The properties file is self-explanatory.
+ </p>
+ </section>
+ </section>
+
+
+
+
+ <section>
+ <title>Installing and running the crawler</title>
+
+ <section>
+ <title>Standalone application</title>
+ <p>
+ In the binary distribution, execute the
+ <code>run</code> script for your operating system
+ (<code>run.bat</code> for windows, and
+ <code>run.sh</code> for unix).
+ </p>
+ </section>
+
+ <section>
+ <title>Web application</title>
+ <p>
+ After deploying the web application, navigate to the
+ application in your browser (e.g.
+ <code>http://localhost:8080/wamblee-crawler-kissweb</code>).
+ The screen should show an overview of the last time it ran (if
+ it ran before) as well as a button to run the crawler immediately.
+ Also, the result of the last run can be viewed.
+ The crawler will run automatically every morning at 5 AM local time,
+ and will retry at 1 hour intervals in case of failure to retrieve
+ programme information.
+ </p>
+ </section>
+
+ <section>
+ <title>Source distribution</title>
+ <p>
+ With the source code, build everything with
+ <code>ant dist-lite</code>, then locate the binary
+ distribution in <code>lib/wamblee/crawler/kiss/kiss-crawler-bin.zip</code>.
+ Then proceed as for the binary distribution.
+ </p>
+ </section>
+
+ <section>
+ <title>General usage</title>
+ <p>
+ When the crawler runs, it
+ retrieves the programs for tomorrow. As a result, it is advisable
+ to run the program at an early point of the day as a scheduled
+ task (e.g. cron on unix). For the web application this is
+ preconfigured at 5AM.
+ </p>
+ <note>
+ If you deploy the web application today, it will run automatically
+ on the next (!) day. This even holds if you deploy the application
+ before the normal scheduled time.
+ </note>
+
+ <p>
+ Modifying the program to allow it to investigate tomorrow's
+ programs instead is easy as well but not yet implemented.
+ </p>
+ </section>
+
+
+ </section>
+
+ <section id="examples">
+ <title>Examples</title>
+
+ <p>
+ The best example is in the distribution itself. It is my personal
+ <code>programs.xml</code> file.
+ </p>
+ </section>
+
+ <section>
+ <title>Contributing</title>
+
+ <p>
+ You are always welcome to contribute. If you find a problem just
+ tell me about it and if you have ideas am I always interested to
+ hear about them.
+ </p>
+ <p>
+ If you are a programmer and have a fix for a bug, just send me a
+ patch and if you are fanatic enough and have ideas, I can also
+ give you write access to the repository.
+ </p>
+ </section>
+
+
+ </body>
+</document>
--- /dev/null
+
+
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>KiSS Crawler overview page</title>
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="expires" content="0">
+
+ <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
+ <meta http-equiv="description" content="This is my page">
+
+ <!--
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ -->
+ </head>
+
+ <body>
+ <h1>KiSS Crawler Overview</h1>
+
+ <TABLE border="1">
+ <tr>
+
+ <td>
+ Currently running:
+ </td>
+ <td>
+ false
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ Last executed at:
+ </td>
+
+ <td>
+ Sat May 06 05:18:54 CEST 2006
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last result:
+ </td>
+ <td>
+ true
+ </td>
+
+ </tr>
+ <tr>
+ <td>
+ Last message:
+ </td>
+ <td>
+
+ </td>
+ </tr>
+ <tr>
+
+ <td>
+ Last report:
+ </td>
+ <td>
+ <a href="details.html">details</a>
+ </td>
+ </tr>
+
+ </TABLE>
+
+ <FORM action="runnow">
+
+ <INPUT type="submit" name="runnow" value="Run Crawler as soon as possible">
+ </FORM>
+
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Ascii Art sample</title>
+ </header>
+ <body>
+ <section>
+ <title>Sample Ascii Art</title>
+ <p>To create a <code>.png</code> image like the one below with ASCII art, just save
+ the text file with the <code>.aart</code> extension and then link from any page
+ as an image (<code><image src="asci-art-file.png"/></code>).</p>
+ <p><img src="cocoon-pyramid.png" alt="cocoon pyramid of management-(logic-content-style)"/></p>
+ <p>Here is the source file that has created the above image.</p>
+ <source>
+
+ +-------------------+
+ | Management |
+ +-+-------+-------+-+
+ | | |
+ | | |
+ +-------+ +----+----+ +-------+
+ | logic +--+ content +--+ style |
+ +-------+ +---------+ +-------+
+
+ </source>
+ <p>An ascii art pad recognized following ascii characters:</p>
+ <ul>
+ <li> '-' horizontal SVG line</li>
+ <li>'|' vertical SVG line</li>
+ <li> '+' corner</li>
+ <li> \ oblique line</li>
+ <li> String starting with letter, digit, or '_' is converted to a SVG text.</li>
+ </ul>
+ </section>
+ </body>
+ <footer>
+ <legal>Copyright 2002-2004 The Apache Software Foundation or its licensors, as applicable.</legal>
+ </footer>
+</document>
--- /dev/null
+
+ +-------------------+
+ | Management |
+ +-+-------+-------+-+
+ | | |
+ | | |
+ +-------+ +----+----+ +-------+
+ | logic +--+ content +--+ style |
+ +-------+ +---------+ +-------+
+
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE greeting PUBLIC "-//Acme//DTD Hello Document V1.0//EN" "hello-v10.dtd">
+<greeting>
+Hello XML Custom World!!
+</greeting>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Custom Schema</title>
+ </header>
+ <body>
+ <p>Forrest comes with a set of schemas for common documents, however, if you have existing documents
+ that use a different schema you will want to tell Forrest how to work with them. The best way of doing
+ this is to <a href="http://forrest.apache.org/0.7/docs/howto/howto-buildPlugin.html">build a plugin</a>
+ so that you can easily reuse the functionality on different projects. Plugins also allow you to share
+ this new functionality with other users, and to benefit from their contributions to your work.</p>
+
+ <p>If you don't want to build a plugin you can make Forrest process them within your project sitemap
+ (but this won't really save you any work since the process is almost the same). This sample site has
+ a demonstration of using a custom DTD. If you request <a href="custom.html"><a href="custom.html"></a>
+ you can see the results. Take a look at the project <code>sitemap.xmap</code> to see how it is done.</p>
+
+ <note>Adding custom schemas with a plugin has the added benefit of being able to add the schema
+ definition to the catalog file rather than having to reference it directly from within the XML
+ document.</note>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.3//EN" "http://forrest.apache.org/dtd/document-v13.dtd">
+<document>
+ <header>
+ <title>The Apache Forrest xdocs document-v1.3 DTD</title>
+ <notice>The content of this document doesn't make any sense at all.</notice>
+ <abstract>This is a demonstration document using all possible elements in
+ the current Apache Forrest xdocs <code>document-v13.dtd</code>
+ </abstract>
+ </header>
+ <body>
+ <note>
+ This is a demonstration document using all possible elements in the
+ current Apache Forrest xdocs <code>document-v13.dtd</code>
+ (See the <link href="#changes">DTD changes</link> section at the bottom.)
+ </note>
+ <section id="sample">
+ <title>Sample Content</title>
+ <p><strong>Hint:</strong> See the xml source to see how the various
+ elements are used and see the
+<!-- FOR-321 workaround
+ <link href="ext:dtd-docs">DTD reference documentation</link>.
+-->
+ <link href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</link>.
+ </p>
+ <section id="block-inline">
+ <title>Block and inline elements</title>
+ <p>This is a simple paragraph. Most documents contain a fair amount of
+ paragraphs. Paragraphs are called <code><p></code>.</p>
+ <p xml:space="preserve"
+ >With the <code><p xml:space="preserve"></code> attribute, you can declare
+ that whitespace should be preserved, without implying it is in any other
+ way special.</p>
+ <p>
+ This next paragraph has a class attribute of 'quote'. CSS can
+ be used to present this <code><p class='quote'></code> in
+ a different style than the other paragraphs. The handling of
+ this quoted paragraph is defined in the <extra-css>
+ element in the skinconf.xml.
+ </p>
+ <p class="quote">
+ Anyway, like I was sayin', shrimp is the fruit of the sea. You can
+ barbecue it, boil it, broil it, bake it, sautee it. Dey's uh,
+ shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried,
+ stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp,
+ pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and
+ potatoes, shrimp burger, shrimp sandwich. That- that's about it.
+ </p>
+ <p>A number of in-line elements are available in the DTD, we will show them
+ inside an unordered list (<code><ul></code>):</p>
+ <ul>
+ <li>Here is a simple list item (<code><li></code>).</li>
+ <li>Have you seen the use of the <code><code></code> element in the
+ previous item?</li>
+ <li>Also, we have <code><sub></code> and <code><sup></code>
+ elements to show content <sup>above</sup> or <sub>below</sub> the text
+ baseline.</li>
+ <li>There is a facility to <em>emphasize</em> certain words using the
+ <code><em></code> <strong><code><strong></code></strong>
+ elements.</li>
+ <li>We can use
+ <icon height="22" width="26" src="../images/icon.png" alt="feather"/>
+ <code><icon></code>s too.</li>
+ <li>Another possibility is the <code><img></code> element:
+ <img src="../images/icon.png" alt="another feather" height="22" width="26"/>,
+ which offers the ability to refer to an image map.</li>
+ <li>We have elements for hyperlinking:
+ <dl>
+ <dt><code><link href="faq.html"></code></dt>
+ <dd>Use this to
+ <link href="faq.html" title="Example of a document via link">link</link>
+ to another document. As per normal, this will open the new document
+ in the same browser window.</dd>
+
+ <dt><code><link href="#section"></code></dt>
+ <dd>Use this to
+ <link href="#section" title="Example of a document via local anchor">link</link>
+ to the named anchor in the current document.
+ </dd>
+
+ <dt><code><link href="faq.html#forrest"></code></dt>
+ <dd>Use this to
+ <link href="faq.html#forrest" title="Example of a document via link and anchor">link</link>
+ to another document and go to the named anchor. This will open
+ the new document in the same browser window.
+ </dd>
+
+ <dt><code><jump href="faq.html"></code></dt>
+ <dd>Use this to
+ <jump href="faq.html" title="Example of a document via jump">jump</jump>
+ to another document and optionally go to a named
+ <jump href="faq.html#forrest" title="Example of a document via jump to anchor">anchor</jump>
+ within that document. This will open the new document in the same
+ browser window. So what is the difference between link and jump?
+ The jump behaves differently, in that it will replace any frames
+ in the current window.
+ This is the equivalent of
+ <code><a ... target="_top"></code>
+ </dd>
+
+ <dt><code><fork href="faq.html"></code></dt>
+ <dd>Use this to
+ <fork href="faq.html" title="Example of a document via fork">fork</fork>
+ your webbrowser to another document. This will open the document
+ in a new, unnamed browser window.
+ This is the equivalent of
+ <code><a ... target="_blank"></code>
+ </dd>
+ </dl></li>
+
+ <li>Oh, by the way, a definition list <code><dl></code> was used inside
+ the previous list item. We could put another
+ <ul>
+ <li>unordered list</li>
+ <li>inside the list item</li>
+ </ul>
+ <table>
+ <caption>A sample nested table</caption>
+ <tr><td>Or even tables.. </td><td>
+ <table><tr><td>inside tables..</td></tr></table>
+ </td></tr>
+ <tr><td>or inside lists, but I believe this liberty gets quickly quite
+ hairy as you see.</td></tr>
+ </table>
+ </li>
+ </ul>
+ <p>So far for the in-line elements, let's look at some paragraph-level
+ elements.</p>
+ <fixme author="SN">The <code><fixme></code> element is used for stuff
+ which still needs work. Mind the <code>author</code> attribute!</fixme>
+ <note>Use the <code><note></code> element to draw attention to something, e.g. ...The <code><code></code> element is used when the author can't
+ express himself clearly using normal sentences ;-)</note>
+ <warning>Sleep deprivation can be the result of being involved in an open
+ source project. (a.k.a. the <code><warning></code> element).
+ </warning>
+ <note label="Important">If you want your own labels for notes and
+ warnings, specify them using the <code>label</code> attribute.
+ </note>
+ <p>Apart from unordered lists, we have ordered lists too, of course.</p>
+ <ol>
+ <li>Item 1</li>
+ <li>Item 2</li>
+ <li>This should be 3 if my math is still OK.</li>
+ </ol>
+ </section>
+
+ <section id="presentations">
+ <title>Various presentation formats</title>
+
+ <p>This sample document, written in document-v13 XML can be presented
+ via Forrest in a number of different formats. The links in the
+ following list show this document in each of the currently available
+ formats.</p>
+
+ <p>Each of the formats can be made available as a link near the top of
+ the page. Actual placement of those links depends on the skin
+ currently in use. Those links are enabled in the skinconf.xml via the
+ <disable-XXX-link> elements in the skinconf.xml</p>
+
+ <table>
+ <tr>
+ <th>Presentation Format</th>
+
+ <th>Description</th>
+
+ <th>skinconf.xml Element</th>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.html">HTML</link></td>
+
+ <td>This document in HTML format. </td>
+
+ <td>Always generated by default. Cannot be turned off.</td>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.xml">XML</link></td>
+
+ <td>This document in its raw XML format.</td>
+
+ <td><disable-xml-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.pdf">PDF</link></td>
+
+ <td>This document as Adobe PDF</td>
+
+ <td><disable-pdf-link>. By default, set to false, meaning
+ that this link will be shown.</td>
+ </tr>
+
+ <tr>
+ <td>Text</td>
+
+ <td><p>This document as straight text.</p>
+ <p>For additional information see the Forrest text-output
+ plugin.</p></td>
+
+ <td><disable-txt-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td>POD</td>
+
+ <td><p>This document as Perl POD (Plain Old Documentation). Text
+ with minimal formatting directives. If on a *nix system with perl
+ installed, see "man perlpod".</p>
+ <p>For additional information see the Forrest pod-output
+ plugin.</p></td>
+
+ <td><disable-pod-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+ </table>
+ </section>
+ <section id="section">
+ <title>Using sections</title>
+ <p>You can use sections to put some structure in your document. For some
+ strange historical reason, the section title is an attribute of the
+ <code><section></code> element.</p>
+ </section>
+ <section id="sub-section">
+ <title>Sections, the sequel</title>
+ <p>Just some second section.</p>
+ <section id="sub-sub-section">
+ <title>Section 2.1</title>
+ <p>Which contains a subsection (2.1).</p>
+ </section>
+ </section>
+
+ <section id="source">
+ <title>Showing preformatted source code</title>
+ <p>Enough about these sections. Let's have a look at more interesting
+ elements, <code><source></code> for instance:</p>
+ <source>
+// This example is from the book _Java in a Nutshell_ by David Flanagan.
+// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates.
+// You may study, use, modify, and distribute this example for any purpose.
+// This example is provided WITHOUT WARRANTY either expressed or implied.
+
+import java.applet.*; // Don't forget these import statements!
+import java.awt.*;
+
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ // The Graphics class is how you do all drawing in Java.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}</source>
+ <p>CDATA sections are used within
+ <code><source></code> elements so that you can write pointy
+ brackets without needing to escape them with messy
+ <code>&lt;</code> entities ...
+ </p>
+ <source><![CDATA[
+<pointy>
+ easy
+</pointy>
+]]></source>
+ <p>Please take care to still use a sensible line-length within your
+ source elements.</p>
+ </section>
+
+ <section id="table">
+ <title>Using tables</title>
+ <p>And now for a table:</p>
+ <table>
+ <caption>Table caption</caption>
+ <tr>
+ <th>heading cell 1</th>
+ <th>heading cell 2</th>
+ <th>heading cell 3</th>
+ </tr>
+ <tr>
+ <td>data cell</td>
+ <td colspan="2">this data cell spans two columns</td>
+ </tr>
+ <tr>
+ <td>
+ Tables can be nested:
+ </td>
+ <td>
+ <table>
+ <tr>
+ <th>column 1</th>
+ <th>column 2</th>
+ </tr>
+ <tr>
+ <td>cell A</td>
+ <td>cell B</td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <ul><li>and can include most other elements</li><li>such as lists</li></ul>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ <anchor id="second-figure-anchor"/>
+ <section id="figure">
+ <title>Using figures</title>
+ <p>And a <code><figure></code> to end all of this.
+ Note that this can also be implemented with an
+ <code><img></code> element.
+ </p>
+ <figure src="../images/project.png" alt="The fine Forrest logo" width="220" height="65"/>
+ </section>
+ </section>
+
+ <section id="changes">
+ <title>DTD changes</title>
+ <p>See the generated
+<!-- FOR-321 workaround
+ <link href="ext:dtd-docs">DTD reference documentation</link>.
+-->
+ <link href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</link>.
+ </p>
+ <section id="changes-13">
+ <title>Changes since document-v12</title>
+ <p>
+ All v1.2 docs will work fine as v1.3 DTD. The main change is the
+ addition of a @class attribute to every element, which enables the
+ "extra-css" section in the skinconf to be put to good use.
+ </p>
+ </section>
+ <section id="changes-12">
+ <title>Changes since document-v11</title>
+ <p>
+ doc-v12 enhances doc-v11 by relaxing various restrictions that were
+ found to be unnecessary.
+ </p>
+ <ul>
+ <li>
+ Links ((link|jump|fork) and inline elements (br|img|icon|acronym) are
+ allowed inside title.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), table and figure|anchor are
+ allowed inside li.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), lists (ol|ul|dl), table,
+ figure|anchor are allowed inside definition lists (dd) and tables (td
+ and dh).
+ </li>
+ <li>
+ Inline content
+ (strong|em|code|sub|sup|br|img|icon|acronym|link|jump|fork) is
+ allowed in strong and em.
+ </li>
+ </ul>
+ </section>
+ </section>
+ </body>
+ <footer>
+ <legal>This is a legal notice, so it is <strong>important</strong>.</legal>
+ </footer>
+</document>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<html>
+<head>
+ <title>Embedded HTML demonstration page</title>
+</head>
+<body>
+
+<h1><a name="intro" />Embedded HTML demonstration page</h1>
+
+<p>An HTML document is used as the source for this page, and translated
+to the intermediate Apache Forrest xdocs document structure. The sitemap then
+does the normal aggregation with the navigation content and application of
+the skin.
+</p>
+
+<p>
+The html is being interpreted by Forrest and transformed to the
+intermediate Apache xdocs document structure. That stylesheet cannot deal
+with every possibility in unstructured html, so it tries to guess how to
+build <section> elements and such.
+It needs <h1> (<h2> etc.) headings in the source html
+in order to identify sections. Patches are welcome to enhance
+that transformer.
+</p>
+
+<p>
+You can still take advantage of Forrest's
+<a href="http://forrest.apache.org/docs/linking.html">"<b>site:<b>"
+method of linking</a>, for example:
+<a href="site:index"><a href="site:index"></a>
+</p>
+
+<hr>
+<note>XHTML can also be used, but it is just treated as interpreted
+html. Future versions of Forrest will take much more advantage of XHTML.
+</note>
+<hr>
+
+<h1><a name="examples" />Some example uses of HTML</h1>
+<p>
+There are situations when the Apache Forrest xdocs DTD is not sufficient.
+The use of embedded HTML enables you to use HTML code in these situations.
+</p>
+
+<h2><a name="js" />Embedded applets and Javascript</h2>
+
+<p>
+See the
+<a href="javascript:alert('Opened with Javascript via the body of the source html.')">Javascript alert pop-up</a>
+</p>
+
+<h2><a name="forms" />HTML forms for user interaction</h2>
+<p>
+Search the Forrest website via Google:
+<!-- Search Google -->
+<form target="_blank" action="http://www.google.com/search" method="get">
+<input value="forrest.apache.org" name="as_sitesearch" type="hidden">
+<input type=hidden name=ie value=UTF-8>
+<input type=hidden name=oe value=UTF-8>
+<a href="http://www.google.com/">
+<img src="http://www.google.com/logos/Logo_40wht.gif"
+border="0" alt="Google Search" align="middle" width="150" height="55"></a>
+<input type="text" name="as_q" size="25" maxlength="255" value="HTML">
+
+<input type="submit" name="btnG" value="Google Search">
+</form>
+<!-- Search Google -->
+</p>
+
+<p>
+See a demonstration of "html" and "html forms" with our
+<a href="http://forrest.apache.org/mirrors.cgi">Forrest download mirror</a>
+facility and the
+<a href="http://forrest.apache.org/howto/howto-asf-mirror.html">explanation</a> howto document.
+</p>
+
+<h2><a name="invalid" />Invalid HTML</h2>
+<p>
+This paragraph has a missing closing tag for the <p> element. If you look
+at the <a href="embedded_html.xml">XML created by Forrest</a> you'll notice that
+Forrest has fixed this.
+
+<h2>Potentially Invalid XDocs</h2>
+
+<warning>However, it should also be noted that the resultant XML is not a valid document
+since it contains the additional HTML elements. If you are intending to use
+the intermediate XDocs for any purpose be aware of this fact.</warning>
+
+<h2><a name="blink" />Other non-standard html-type abilities</h2>
+<p>
+Use other HTML <blink>delights (???) and tricks</blink>.
+</p>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE faqs PUBLIC "-//APACHE//DTD FAQ V2.0//EN" "http://forrest.apache.org/dtd/faq-v20.dtd">
+
+<faqs>
+ <title>Frequently Asked Questions</title>
+
+ <faqsection id="docs">
+ <title>Documentation</title>
+ <faq id="forrest">
+ <question>
+ How can I help write documentation?
+ </question>
+ <answer>
+ <p>
+ This project uses <a href="ext:forrest">Apache Forrest</a> to
+ generate documentation from XML. Please download a copy of Forrest,
+ which can be used to <a
+ href="ext:forrest/validation">validate</a>, <a
+ href="ext:forrest/webapp">develop</a> and render a project site.
+ </p>
+ </answer>
+ </faq>
+ <!-- More faqs or parts here -->
+ </faqsection>
+ <!-- More faqs or parts here -->
+</faqs>
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!-- ===================================================================
+
+ Apache Hello Document DTD (Version 1.1)
+
+PURPOSE:
+ This DTD was developed to create a sample of custom document.
+
+TYPICAL INVOCATION:
+
+ <!DOCTYPE greeting PUBLIC
+ "-//APACHE//DTD Hello Document Vx.y//EN"
+ "hello-vxy.dtd">
+
+ where
+
+ x := major version
+ y := minor version
+
+NOTES:
+
+FIXME:
+
+CHANGE HISTORY:
+[Version 1.0]
+ 20050112 Initial version. (JJP)
+
+==================================================================== -->
+
+<!ELEMENT greeting (#PCDATA)>
+
+<!-- =============================================================== -->
+<!-- End of DTD -->
+<!-- =============================================================== -->
--- /dev/null
+%PDF-1.3
+%ª«¬
+4 0 obj
+<< /Type /Info
+/Producer (FOP 0.20.4) >>
+endobj
+5 0 obj
+<< /Length 203 /Filter [ /ASCII85Decode /FlateDecode ]
+ >>
+stream
+Gar'!]afWZ&;9q-MRA)RFnblL2&]tQSZsjOOT[ck2SQkp(bfQ[R7ZPq=U24c0dqq_i?B[A.0s\)5f5<IA'lb0eeo`C+`q\Ip/Tke*)7%T+.hT8:QQidXoPLKZM,RXY"bP+;E@%,ZX;V'Aq+M9rH"!g=N5TToDMoqMeUiEe).I_W3q80:jF+;'9bVIeBRb]DhE9:E2be2~>
+endstream
+endobj
+6 0 obj
+<< /Type /Page
+/Parent 1 0 R
+/MediaBox [ 0 0 595 842 ]
+/Resources 3 0 R
+/Contents 5 0 R
+>>
+endobj
+7 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F1
+/BaseFont /Helvetica
+/Encoding /WinAnsiEncoding >>
+endobj
+8 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F5
+/BaseFont /Times-Roman
+/Encoding /WinAnsiEncoding >>
+endobj
+1 0 obj
+<< /Type /Pages
+/Count 1
+/Kids [6 0 R ] >>
+endobj
+2 0 obj
+<< /Type /Catalog
+/Pages 1 0 R
+ >>
+endobj
+3 0 obj
+<<
+/Font << /F1 7 0 R /F5 8 0 R >>
+/ProcSet [ /PDF /ImageC /Text ] >>
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000687 00000 n
+0000000745 00000 n
+0000000795 00000 n
+0000000015 00000 n
+0000000071 00000 n
+0000000365 00000 n
+0000000471 00000 n
+0000000578 00000 n
+trailer
+<<
+/Size 9
+/Root 2 0 R
+/Info 4 0 R
+>>
+startxref
+883
+%%EOF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Samples</title>
+ </header>
+ <body>
+ <section id="please-contribute">
+ <title>If something goes wrong..</title>
+ <p>Patches are welcome: <a href="http://forrest.apache.org/docs/faq.html">Forrest FAQ</a></p>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://apache.org/forrest/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Demonstration of linking</title>
+ </header>
+
+ <body>
+ <section id="overview">
+ <title>Overview</title>
+ <p>Forrest has many powerful techniques for linking between documents
+ and for managing the site navigation. This document demonstrates those
+ techniques.
+ The document "<a href="ext:linking">Menus and Linking</a>"
+ has the full details.
+ </p>
+ </section>
+
+ <section id="uri-space">
+ <title>Building and maintaining consistent URI space</title>
+ <p>
+ When Forrest builds your site, it starts from the front page. Like
+ a robot, it traverses all of the links that it finds in the documents
+ and builds the corresponding pages. Any new links are further traversed.
+ </p>
+ <p>
+ Sometimes those links lead to documents that are generated directly
+ from xml source files, sometimes they are generated from other source
+ via an intermediate xml format. Other times the links lead to raw
+ un-processed content.
+ </p>
+ <p>
+ The site navigation configuration file "<code>site.xml</code>" provides
+ a way to manage this URI space. In the future, when documents are
+ re-arranged and renamed, the site.xml configuration will enable this
+ smoothly.
+ </p>
+ </section>
+
+ <section id="resource-space">
+ <title>Mapping the local resource space to the final URI space</title>
+ <p>
+ For both generated and raw (un-processed) files, the top-level of the
+ URI space corresponds to the "<code>content/xdocs/</code>" directory,
+ i.e. the location of the "<code>site.xml</code>" configuration file.
+ </p>
+ <note>
+ In versions prior to 0.7 raw un-processed content was stored in
+ the "<code>content/</code>" directory. In 0.7 onwards, raw
+ un-processed data is stored alongside the xdocs. In addition,
+ in 0.6 and earlier, HTML documents could be stored in the xdocs
+ directory and served without processing. If you
+ you wish to emulate the behaviour of 0.6 and earlier see the
+ next section.
+ </note>
+ <p>
+ A diagram will help.
+ </p>
+ <source><![CDATA[
+The resource space ==============> The final URI space
+------------------ -------------------
+Generated content ...
+ content/xdocs/index.xml index.html
+ content/xdocs/samples/index.xml samples/index.html
+ content/xdocs/samples/faq.xml samples/faq.html
+ content/xdocs/test1.html test1.html
+ content/xdocs/samples/test3.html samples/test3.html
+ content/xdocs/samples/subdir/test4.html samples/subdir/test4.html
+
+Raw un-processed content ...
+ content/xdocs/hello.pdf hello.pdf
+ content/xdocs/hello.sxw hello.sxw
+ content/xdocs/subdir/hello.sxw subdir/hello.sxw
+]]></source>
+
+ <section>
+ <title>How Plugins May Affect The URI Space</title>
+ <p>By using <a href="site:plugins">Forrest Input Plugins</a>
+ you can process some file formats, such as
+ OpenOffice.org documents and produce processed content from them. For example,
+ the file <code>content/xdocs/hello.sxw</code> can be used to produce a
+ skinned version of the document at with the name <code>hello.html</code>.
+ Similarly, you can use <a href="site:plugins">Forrest Output
+ Plugins</a> to create different output formats such as PDF, in this
+ case <code>content/xdocs/hello.sxw</code> can produce
+ <code>hello.pdf</code>.</p>
+
+ <p>However, this does not affect the handling of raw content. That is, you
+ can still retrieve the raw un-processed version with, for example,
+ <code>hello.sxw</code>. If you want to prevent the user retrieving the
+ un-processed version you will have to create matchers that intercept
+ these requests within your project sitemap.</p>
+ </section>
+
+ </section>
+
+ <section id="generated">
+ <title>Basic link to internal generated pages</title>
+ <p>
+ When this type of link is encountered, Forrest will look for a
+ corresponding xml file, relative to this document (i.e. in
+ <code>content/xdocs/samples/</code>).
+ </p>
+ <p>A generated document in the current directory, which corresponds to
+ <code>content/xdocs/samples/sample.html</code> ...
+ </p>
+ <source><![CDATA[<a href="sample.html">]]><a href="sample.html">sample.html</a><![CDATA[</a>]]></source>
+ <p>In a sub-directory, which corresponds to
+ <code>content/xdocs/samples/subdir/index.html</code> ...
+ </p>
+ <source><![CDATA[<a href="subdir/index.html">]]><a href="subdir/index.html">subdir/index.html</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="raw">
+ <title>Basic link to raw un-processed content</title>
+ <p>
+ Raw content files are not intended for any processing, they are just
+ linked to (e.g. pre-prepared PDFs, zip archives).
+ These files are placed alongside your normal content in the
+ "<code>content/xdocs</code>" directory.
+ </p>
+ <p>A raw document in the current directory, which corresponds to
+ <code>content/xdocs/samples/helloAgain.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="helloAgain.pdf">]]><a href="helloAgain.pdf">helloAgain.pdf</a><![CDATA[</a>]]></source>
+ <p>A raw document in a sub-directory, which corresponds to
+ <code>content/xdocs/samples/subdir/hello.zip</code> ...
+ </p>
+ <source><![CDATA[<a href="subdir/hello.zip">]]><a href="subdir/hello.zip">subdir/hello.zip</a><![CDATA[</a>]]></source>
+ <p>A raw document at the next level up, which corresponds to
+ <code>content/hello.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="../hello.pdf">]]><a href="../hello.pdf">../hello.pdf</a><![CDATA[</a>]]></source>
+
+ <section>
+ <title>Serving (X)HTML content without Skinning</title>
+
+ <p>Prior to version 0.7, the raw un-processed content was stored in
+ the "<code>content/</code>" directory. In 0.7 onwards, raw
+ un-processed data is stored alongside the xdocs. In addition
+ in 0.6 and earlier, HTML files could be stored in the xdocs
+ directory and they would be served without further processing.
+ As described above, this is not the case in 0.7 where HTML files
+ are, by default, skinned by Forrest.</p>
+
+ <p>If you
+ you wish to emulate the behaviour of 0.6 and earlier then you
+ must add the following to your project sitemap.</p>
+
+ <source>
+<map:match pattern="**.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{0}">
+ <map:read src="{project:content}/{0}" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ <map:when test="{project:content.xdocs}{0}">
+ <map:read src="{project:content.xdocs}/{0}" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content.xdocs}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ </map:select>
+</map:match>
+ </source>
+
+ <p>The above allows us to create links to un-processed skinned files stored
+ in the <code>{project:content}</code> or <code>{project:content.xdocs}</code>
+ directory. For example:
+ <a href="/test1.html">HTML content</a>. However, it will
+ break the 0.7 behaviour of skinning HTML content. For this reason the old
+ ".ehtml" extension can be used to embed HTML content in a Forrest skinned
+ site </p>
+
+ <p>Note that you can change the matchers above to selectively serve some
+ content as raw un-processed content, whilst still serving other content
+ as skinned documents. For example, the following snippet would allow
+ you to serve the content of an old, deprecated site without processing
+ from Forrest, whilst still allowing all other content to be processed
+ by Forrest in the normal way:</p>
+
+ <source>
+<map:match pattern="old_site/**.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{1}.html">
+ <map:read src="{project:content}/{1}.html" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+</map:match>
+ </source>
+
+ <p>For example, <a href="/old_site/test1.html">HTML content</a>.</p>
+ </section>
+ </section>
+
+ <section id="url">
+ <title>Full URL to external documents</title>
+ <p>A full URL ...</p>
+ <source><![CDATA[<a href="http://forrest.apache.org/">]]><a href="http://forrest.apache.org/">http://forrest.apache.org/</a><![CDATA[</a>]]></source>
+ <p>A full URL with a fragment identifier ...</p>
+ <source><![CDATA[<a href="http://forrest.apache.org/faq.html#link_raw">]]><a href="http://forrest.apache.org/faq.html#link_raw">http://forrest.apache.org/faq.html#link_raw</a><![CDATA[</a>]]></source>
+ <p>
+ Note that Forrest does not traverse external links to look for
+ other links.
+ </p>
+ </section>
+
+ <section id="site">
+ <title>Using site.xml to manage the links</title>
+ <p>As you will have discovered, using pathnames with ../../ etc. will
+ get very nasty. Real problems occur when you use a smart text editor
+ that tries to manage the links for you. For example, it will have
+ trouble linking to the raw content files which are not yet in their
+ final location.
+ </p>
+ <p>
+ Links and filenames are bound to change and re-arrange. It is
+ essential to only change those links in one central place, not in every
+ document.
+ </p>
+ <p>
+ The "<code>site.xml</code>" configuration file to the rescue. It maps
+ symbolic names to actual resources.
+ </p>
+
+ <section id="site-simple">
+ <title>Basic link to internal generated pages</title>
+ <p>This single entry ...</p>
+ <source><![CDATA[<index label="Index" href="index.html"/>]]></source>
+ <p>
+ enables a simple link to a generated document, which corresponds to
+ <code>content/xdocs/index.xml</code> ...
+ </p>
+ <source><![CDATA[<a href="site:index">]]><a href="site:index">site:index</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="site-compound">
+ <title>Group some items</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <samples label="Samples" href="samples/" tab="samples">
+ <faq label="FAQ" href="faq.html"/>
+ ...
+ </samples>
+]]></source>
+ <p>
+ enables a link to a generated document, which corresponds to
+ <code>content/xdocs/samples/index.xml</code> ...
+ </p>
+ <source><![CDATA[<a href="site:samples">]]><a href="site:samples">site:samples</a><![CDATA[</a>]]></source>
+ <p>
+ and a link to a generated document, which corresponds to
+ <code>content/xdocs/samples/faq.xml</code> ...
+ </p>
+ <source>
+<![CDATA[<a href="site:faq">]]><a href="site:faq">site:faq</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="site:samples/faq">]]><a href="site:samples/faq">site:samples/faq</a><![CDATA[</a>]]>
+ </source>
+ </section>
+
+ <section id="site-fragment">
+ <title>Fragment identifiers</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <samples label="Samples" href="samples/" tab="samples">
+ <sample label="Apache document" href="sample.html">
+ <top href="#top"/>
+ <section href="#section"/>
+ </sample>
+ ...
+ </samples>
+]]></source>
+ <p>
+ enables a link to a fragment identifier within the
+ <code>samples/sample.html</code> document ...
+ </p>
+ <source><![CDATA[<a href="site:samples/sample/section">]]><a href="site:samples/sample/section">site:samples/sample/section</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="site-raw">
+ <title>Define items for raw content</title>
+ <p>This entry ...</p>
+ <source><![CDATA[<hello_print href="hello.pdf"/>]]></source>
+ <p>
+ enables a link to a raw document, which corresponds to
+ <code>content/hello.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="site:hello_print">]]><a href="site:hello_print">site:hello_print</a><![CDATA[</a>]]></source>
+
+ </section>
+
+ <section id="site-ext">
+ <title>External links</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <external-refs>
+ <forrest href="http://forrest.apache.org/">
+ <linking href="docs/linking.html"/>
+ <webapp href="docs/your-project.html#webapp"/>
+ </forrest>
+ </external-refs>
+]]></source>
+ <p>
+ enables a link to an external URL ...
+ </p>
+ <source><![CDATA[<a href="ext:forrest">]]><a href="ext:forrest">ext:forrest</a><![CDATA[</a>]]></source>
+ <p>
+ and a link to another external URL ...
+ </p>
+ <source>
+<![CDATA[<a href="ext:linking">]]><a href="ext:linking">ext:linking</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="ext:forrest/linking">]]><a href="ext:forrest/linking">ext:forrest/linking</a><![CDATA[</a>]]>
+ </source>
+ <p>
+ and a link to another external URL with a fragment identifier ...
+ </p>
+ <source>
+<![CDATA[<a href="ext:webapp">]]><a href="ext:webapp">ext:webapp</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="ext:forrest/webapp">]]><a href="ext:forrest/webapp">ext:forrest/webapp</a><![CDATA[</a>]]>
+ </source>
+ </section>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>The Apache Forrest xdocs document-v2.0 DTD</title>
+ <notice>The content of this document doesn't make any sense at all.</notice>
+ <abstract>This is a demonstration document using all possible elements in
+ the current Apache Forrest xdocs <code>document-v20.dtd</code>
+ </abstract>
+ </header>
+ <body>
+ <note>
+ This is a demonstration document using all possible elements in the
+ current Apache Forrest xdocs <code>document-v20.dtd</code>
+ (See the <a href="#changes">DTD changes</a> section at the bottom.)
+ </note>
+ <section id="sample">
+ <title>Sample Content</title>
+ <p><strong>Hint:</strong> See the xml source to see how the various
+ elements are used and see the
+<!-- FOR-321 workaround
+ <a href="ext:dtd-docs">DTD reference documentation</a>.
+-->
+ <a href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</a>.
+ </p>
+ <section id="block-inline">
+ <title>Block and inline elements</title>
+ <p>This is a simple paragraph. Most documents contain a fair amount of
+ paragraphs. Paragraphs are called <code><p></code>.</p>
+ <p xml:space="preserve"
+ >With the <code><p xml:space="preserve"></code> attribute, you can declare
+ that whitespace should be preserved, without implying it is in any other
+ way special.</p>
+ <p>
+ This next paragraph has a class attribute of 'quote'. CSS can
+ be used to present this <code><p class='quote'></code> in
+ a different style than the other paragraphs. The handling of
+ this quoted paragraph is defined in the <extra-css>
+ element in the skinconf.xml.
+ </p>
+ <p class="quote">
+ Anyway, like I was sayin', shrimp is the fruit of the sea. You can
+ barbecue it, boil it, broil it, bake it, sautee it. Dey's uh,
+ shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried,
+ stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp,
+ pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and
+ potatoes, shrimp burger, shrimp sandwich. That- that's about it.
+ </p>
+ <p>A number of in-line elements are available in the DTD, we will show them
+ inside an unordered list (<code><ul></code>):</p>
+ <ul>
+ <li>Here is a simple list item (<code><li></code>).</li>
+ <li>Have you seen the use of the <code><code></code> element in the
+ previous item?</li>
+ <li>Also, we have <code><sub></code> and <code><sup></code>
+ elements to show content <sup>above</sup> or <sub>below</sub> the text
+ baseline.</li>
+ <li>There is a facility to <em>emphasize</em> certain words using the
+ <code><em></code> <strong><code><strong></code></strong>
+ elements.</li>
+ <li>We can use
+ <icon height="22" width="26" src="../images/icon.png" alt="feather"/>
+ <code><icon></code>s too.</li>
+ <li>Another possibility is the <code><img></code> element:
+ <img src="../images/icon.png" alt="another feather" height="22" width="26"/>,
+ which offers the ability to refer to an image map.</li>
+ <li>We have elements for hyperlinking:
+ <dl>
+ <dt><code><a href="faq.html"></code></dt>
+ <dd>Use this to
+ <a href="faq.html" title="Example of a document via link">link</a>
+ to another document. As per normal, this will open the new document
+ in the same browser window.</dd>
+
+ <dt><code><a href="#section"></code></dt>
+ <dd>Use this to
+ <a href="#section" title="Example of a document via local anchor">link</a>
+ to the named anchor in the current document.
+ </dd>
+
+ <dt><code><a href="faq.html#forrest"></code></dt>
+ <dd>Use this to
+ <a href="faq.html#forrest" title="Example of a document via link and anchor">link</a>
+ to another document and go to the named anchor. This will open
+ the new document in the same browser window.
+ </dd>
+ <dt>Targetted window control with jump and fork.</dt>
+ <dd>See demonstration
+ <a href="#link-class">using class attribute on links</a>.
+ </dd>
+ </dl></li>
+
+ <li>Oh, by the way, a definition list <code><dl></code> was used inside
+ the previous list item. We could put another
+ <ul>
+ <li>unordered list</li>
+ <li>inside the list item</li>
+ </ul>
+ <table>
+ <caption>A sample nested table</caption>
+ <tr><td>Or even tables.. </td><td>
+ <table><tr><td>inside tables..</td></tr></table>
+ </td></tr>
+ <tr><td>or inside lists, but I believe this liberty gets quickly quite
+ hairy as you see.</td></tr>
+ </table>
+ </li>
+ </ul>
+ <p>So far for the in-line elements, let's look at some paragraph-level
+ elements.</p>
+ <fixme author="SN">The <code><fixme></code> element is used for stuff
+ which still needs work. Mind the <code>author</code> attribute!</fixme>
+ <note>Use the <code><note></code> element to draw attention to something, e.g. ...The <code><code></code> element is used when the author can't
+ express himself clearly using normal sentences ;-)</note>
+ <warning>Sleep deprivation can be the result of being involved in an open
+ source project. (a.k.a. the <code><warning></code> element).
+ </warning>
+ <note label="Important">If you want your own labels for notes and
+ warnings, specify them using the <code>label</code> attribute.
+ </note>
+ <p>Apart from unordered lists, we have ordered lists too, of course.</p>
+ <ol>
+ <li>Item 1</li>
+ <li>Item 2</li>
+ <li>This should be 3 if my math is still OK.</li>
+ </ol>
+ </section>
+
+ <section id="presentations">
+ <title>Various presentation formats</title>
+
+ <p>This sample document, written in document-v20 XML can be presented
+ via Forrest in a number of different formats. The links in the
+ following list show this document in each of the currently available
+ formats.</p>
+
+ <p>Each of the formats can be made available as a link near the top of
+ the page. Actual placement of those links depends on the skin
+ currently in use. Those links are enabled in the skinconf.xml via the
+ <disable-XXX-link> elements in the skinconf.xml</p>
+
+ <table>
+ <tr>
+ <th>Presentation Format</th>
+
+ <th>Description</th>
+
+ <th>skinconf.xml Element</th>
+ </tr>
+
+ <tr>
+ <td><a href="sample.html">HTML</a></td>
+
+ <td>This document in HTML format. </td>
+
+ <td>Always generated by default. Cannot be turned off.</td>
+ </tr>
+
+ <tr>
+ <td><a href="sample.xml">XML</a></td>
+
+ <td>This document in its raw XML format.</td>
+
+ <td><disable-xml-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td><a href="sample.pdf">PDF</a></td>
+
+ <td>This document as Adobe PDF</td>
+
+ <td><disable-pdf-link>. By default, set to false, meaning
+ that this link will be shown.</td>
+ </tr>
+
+ <tr>
+ <td>Text</td>
+
+ <td><p>This document as straight text.</p>
+ <p>For additional information see the Forrest text-output
+ plugin.</p></td>
+
+ <td><disable-txt-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td>POD</td>
+
+ <td><p>This document as Perl POD (Plain Old Documentation). Text
+ with minimal formatting directives. If on a *nix system with perl
+ installed, see "man perlpod".</p>
+ <p>For additional information see the Forrest pod-output
+ plugin.</p></td>
+
+ <td><disable-pod-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+ </table>
+ </section>
+ <section id="section">
+ <title>Using sections</title>
+ <p>You can use sections to put some structure in your document. For some
+ strange historical reason, the section title is an attribute of the
+ <code><section></code> element.</p>
+ </section>
+ <section id="sub-section">
+ <title>Sections, the sequel</title>
+ <p>Just some second section.</p>
+ <section id="sub-sub-section">
+ <title>Section 2.1</title>
+ <p>Which contains a subsection (2.1).</p>
+ </section>
+ </section>
+
+ <section id="source">
+ <title>Showing preformatted source code</title>
+ <p>Enough about these sections. Let's have a look at more interesting
+ elements, <code><source></code> for instance:</p>
+ <source>
+// This example is from the book _Java in a Nutshell_ by David Flanagan.
+// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates.
+// You may study, use, modify, and distribute this example for any purpose.
+// This example is provided WITHOUT WARRANTY either expressed or implied.
+
+import java.applet.*; // Don't forget these import statements!
+import java.awt.*;
+
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ // The Graphics class is how you do all drawing in Java.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}</source>
+ <p>CDATA sections are used within
+ <code><source></code> elements so that you can write pointy
+ brackets without needing to escape them with messy
+ <code>&lt;</code> entities ...
+ </p>
+ <source><![CDATA[
+<pointy>
+ easy
+</pointy>
+]]></source>
+ <p>Please take care to still use a sensible line-length within your
+ source elements.</p>
+ </section>
+
+ <section id="table">
+ <title>Using tables</title>
+ <p>And now for a table:</p>
+ <table>
+ <caption>Table caption</caption>
+ <tr>
+ <th>heading cell 1</th>
+ <th>heading cell 2</th>
+ <th>heading cell 3</th>
+ </tr>
+ <tr>
+ <td>data cell</td>
+ <td colspan="2">this data cell spans two columns</td>
+ </tr>
+ <tr>
+ <td>
+ Tables can be nested:
+ </td>
+ <td>
+ <table>
+ <tr>
+ <th>column 1</th>
+ <th>column 2</th>
+ </tr>
+ <tr>
+ <td>cell A</td>
+ <td>cell B</td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <ul><li>and can include most other elements</li><li>such as lists</li></ul>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ <anchor id="second-figure-anchor"/>
+ <section id="figure">
+ <title>Using figures</title>
+ <p>And a <code><figure></code> to end all of this.
+ Note that this can also be implemented with an
+ <code><img></code> element.
+ </p>
+ <figure src="../images/project.png" alt="The fine Forrest logo" width="220" height="65"/>
+ </section>
+ <section id="link-class">
+ <title>Using class attribute on links</title>
+
+ <p>The document-v13 had elements <fork> and <jump>. In
+ document-v20, those elements no longer exist but the functionality can
+ be duplicated by using the @class attribute.
+ Even though the opening of separate windows should be under the
+ control of the user, these techniques can still be employed.</p>
+
+ <table>
+ <tr>
+ <th><p>Document V1.3</p></th>
+
+ <th><p>Document V2.0</p></th>
+ </tr>
+
+ <tr>
+ <td><p><fork href="faq.html"></p></td>
+
+ <td><a class="fork" href="faq.html"><a class="fork"
+ href="faq.html"></a></td>
+ </tr>
+
+ <tr>
+ <td><p><jump href="faq.html"></p></td>
+
+ <td><p><a class="jump" href="faq.html"><a class="jump"
+ href="faq.html"></a></p></td>
+ </tr>
+ </table>
+ </section>
+ </section>
+
+ <section id="changes">
+ <title>DTD changes</title>
+ <p>See the generated
+<!-- FOR-321 workaround
+ <a href="ext:dtd-docs">DTD reference documentation</a>.
+-->
+ <a href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</a>.
+ </p>
+ <section id="changes-20">
+ <title>Changes between document-v13 and document-v20</title>
+ <ul>
+ <li>Renamed <strong><link></strong>
+ to <strong><a></strong>
+ </li>
+ <li>Removed <strong><fork></strong>
+ and <strong><jump></strong> in favour of the
+ <strong><a></strong> element. See demonstration
+ <a href="#link-class">using class attribute on links</a>.
+ </li>
+ </ul>
+ </section>
+ <section id="changes-13">
+ <title>Changes between document-v12 and document-v13</title>
+ <p>
+ All v1.2 docs will work fine as v1.3 DTD. The main change is the
+ addition of a @class attribute to every element, which enables the
+ "extra-css" section in the skinconf to be put to good use.
+ </p>
+ </section>
+ <section id="changes-12">
+ <title>Changes between document-v11 and document-v12</title>
+ <p>
+ doc-v12 enhances doc-v11 by relaxing various restrictions that were
+ found to be unnecessary.
+ </p>
+ <ul>
+ <li>
+ Links ((link|jump|fork) and inline elements (br|img|icon|acronym) are
+ allowed inside title.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), table and figure|anchor are
+ allowed inside li.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), lists (ol|ul|dl), table,
+ figure|anchor are allowed inside definition lists (dd) and tables (td
+ and dh).
+ </li>
+ <li>
+ Inline content
+ (strong|em|code|sub|sup|br|img|icon|acronym|link|jump|fork) is
+ allowed in strong and em.
+ </li>
+ </ul>
+ </section>
+ </section>
+ </body>
+ <footer>
+ <legal>This is a legal notice, so it is <strong>important</strong>.</legal>
+ </footer>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Static content - including raw un-processed files and documents</title>
+ </header>
+ <body>
+ <section>
+ <title>Linking to static content</title>
+ <p>
+ You can place some types of raw content into the xdocs directory. For example,
+ you can place a PDF file in <code>src/documentation/content/xdocs</code> and link
+ to it normally,
+ <strong><a href="../hello.pdf"></strong><a href="../hello.pdf">hello.pdf</a><strong></a></strong>
+ However, note that if the file is one that Forrest is able to process, for example
+ an HTML file, these files will be processed accordingly.</p>
+
+ <p>
+ It is also worth noting that files in the xdocs directory will only be copied
+ into your final site if there is a link to them somewhere in the site. See the next
+ section for details of how to include content that is not linked.</p>
+
+ <p>
+ For more information see the
+ <a href="site:linking">Linking demonstration</a>.</p>
+ </section>
+
+ <section>
+ <title>Including Static Content that is Not Linked</title>
+
+ <p>
+ You can include raw HTML, PDFs, plain-text, and other files. In your final site by
+ placing them in the <code>src/documentation/content</code> directory. Files in this
+ directory will be copied over automatically but will not be processed in any way by
+ Forrest, that is they will be linked to as raw files.</p>
+
+ <p>
+ You can also have sub-directories such as
+ <code>src/documentation/content/samples/subdir/</code> which
+ reflects your main
+ <code>xdocs/</code> tree. The raw files will then end up
+ beside your documents.
+ </p>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE book PUBLIC "-//APACHE//DTD Cocoon Documentation Book V1.0//EN" "http://forrest.apache.org/dtd/book-cocoon-v10.dtd">
+
+<!-- Sample book.xml file. If this file is renamed to 'book.xml', it will be
+used to define the menu in this subdirectory, instead of that generated from
+the usual site.xml mechanism. The normal relative and absolute hrefs are also
+available. -->
+
+<book software="MyProj"
+ title="MyProj"
+ copyright="@year@ The Apache Software Foundation"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <menu label="About">
+ <!-- using the normal site.xml linking mechanism -->
+ <menu-item label="Index" href="site:index"/>
+ <!-- using relative URIs with relative path -->
+ <menu-item label="Sample page" href="../sample.html"/>
+ <!-- using relative URIs with absolute path -->
+ <menu-item label="Sample ihtml page" href="/samples/ihtml-sample.html"/>
+ <!-- using the normal site.xml linking mechanism -->
+ <menu-item label="FAQ" href="site:faq"/>
+ <menu-item label="Changes" href="site:changes"/>
+ <menu-item label="Todo" href="site:todo"/>
+ </menu>
+
+ <menu label="Subdir">
+ <menu-item label="index" href="site:subdir/index"/>
+ </menu>
+
+</book>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Page generated from a sub-directory</title>
+
+ <authors>
+ <person name="Joe Bloggs" email="joe@joescompany.org" />
+ </authors>
+ </header>
+
+ <body>
+ <section>
+ <title>A sub-directory</title>
+ <p>This was generated from a sub-directory.</p>
+ <p>When creating new subdirectories, remember that these <em>must</em>
+ be declared in site.xml or each directory must have a book.xml file.
+ </p>
+ </section>
+ </body>
+</document>
+
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Interactive client-side imagemaps - the usemap attribute</title>
+ </header>
+ <body>
+ <section id="demo">
+ <title>Imagemap demo</title>
+ <p>
+ <img src="/images/usemap.gif" usemap="#my-map"
+ alt="usemap demo" width="256" height="256"/>
+ </p>
+ <p>
+ <map name="my-map">
+ <area shape="rect" coords="173,14,240,71"
+ alt="Rectangle" href="ext:forrest"/>
+ <area shape="circle" coords="53,172,28"
+ alt="Circle" href="../index.html"/>
+ <area shape="default" coords="0,0.256,256"
+ alt="Default" href="http://www.apache.org"/>
+ </map>
+ </p>
+ </section>
+ <section id="source">
+ <title>Source code</title>
+ <source><![CDATA[
+ <p>
+ <img src="/images/usemap.gif" usemap="#my-map"
+ alt="usemap demo" width="256" height="256"/>
+ </p>
+ <p>
+ <map name="my-map">
+ <area shape="rect" coords="173,14,240,71"
+ alt="Rectangle" href="ext:forrest"/>
+ <area shape="circle" coords="53,172,28"
+ alt="Circle" href="../index.html"/>
+ <area shape="default" coords="0,0.256,256"
+ alt="Default" href="http://www.apache.org"/>
+ </map>
+ </p>
+]]></source>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+Forrest site.xml
+
+This file contains an outline of the site's information content. It is used to:
+- Generate the website menus (though these can be overridden - see docs)
+- Provide semantic, location-independent aliases for internal 'site:' URIs, eg
+<link href="site:changes"> links to changes.html (or ../changes.html if in
+ subdir).
+- Provide aliases for external URLs in the external-refs section. Eg, <link
+ href="ext:cocoon"> links to http://cocoon.apache.org/
+
+See http://forrest.apache.org/docs/linking.html for more info
+-->
+
+<site label="MyProj" href="" xmlns="http://apache.org/forrest/linkmap/1.0" tab="">
+ <!-- Note: No matter what you configure here, Forrest will always try to load
+ index.html when you request http://yourHost/.
+ 'How can I use a start-up-page other than index.html?' in the FAQs has more
+ information tells you how to change that.
+ -->
+ <about label="About">
+ <index label="Index" href="index.html" description="Welcome to the KiSS crawler"/>
+ </about>
+
+ <!-- samples label="Samples" href="samples/" tab="samples">
+ <index href="index.html"/>
+ <sample label="Apache doc v2.0" href="sample.html"
+ description="A nonsense document using all possible elements in the current document v2.0">
+ <top href="#top"/>
+ <section href="#section"/>
+ </sample>
+ <document-v13 label="Apache doc v1.3" href="document-v13.html"
+ description="A nonsense document using all possible elements in the document v1.3">
+ <top href="#top"/>
+ <section href="#section"/>
+ </document-v13>
+ <static label="Static content" href="static.html"
+ description="Static raw un-processed content" />
+ <linking label="Linking" href="linking.html"
+ description="Linking explained and demonstrated" />
+ <sample-html label="Embedded HTML" href="embedded_html.html"
+ description="Test of Embedded HTML" />
+ <sample-ascii-art label="ascii-art page" href="ascii-art.html"
+ description="Sample Ascii Art page" />
+ <sample-usemap label="usemap" href="usemap.html"
+ description="Client-side imagemap" />
+ <sample-custom label="User Schemas" href="customSchema.html"
+ description="Custom XML Schemas"/>
+ <custom label="Custom File" href="custom.html" description="A custom XML file"/>
+ <faq label="FAQ" href="faq.html" description="Frequently Asked Questions" />
+ <subdir label="Subdir" href="subdir/">
+ <index label="Index" href="index.html"
+ description="Page generated from a sub-directory"/>
+ </subdir>
+ </samples -->
+
+ <!-- plugins label="Plugins" href="pluginDocs/plugins_0_70/" tab="plugins">
+ <index label="Index" href="index.html" description="List of plugins available for Forrest"/>
+ </plugins -->
+
+ <files>
+ <hello_print href="hello.pdf" />
+ <test1 href="test1.html" />
+ </files>
+
+
+
+ <!--
+ The href must be wholesite.html/pdf You can change the labels and node names
+ <all label="All">
+ <whole_site_html label="Whole Site HTML" href="wholesite.html"/>
+ <whole_site_pdf label="Whole Site PDF" href="wholesite.pdf"/>
+ </all>
+ -->
+
+ <links>
+ <kiss href="http://www.kiss-technology.com"/>
+ </links>
+
+ <external-refs>
+ <forrest href="http://forrest.apache.org/">
+ <linking href="docs/linking.html"/>
+ <validation href="docs/validation.html"/>
+ <webapp href="docs/your-project.html#webapp"/>
+ <dtd-docs href="docs/dtd-docs.html"/>
+ </forrest>
+ <cocoon href="http://cocoon.apache.org/"/>
+ <xml.apache.org href="http://xml.apache.org/"/>
+ </external-refs>
+
+</site>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE tabs PUBLIC "-//APACHE//DTD Cocoon Documentation Tab V1.1//EN" "http://forrest.apache.org/dtd/tab-cocoon-v11.dtd">
+
+<tabs software="MyProj"
+ title="MyProj"
+ copyright="Foo"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <!-- The rules for tabs are:
+ @dir will always have '/@indexfile' added.
+ @indexfile gets appended to @dir if the tab is selected. Defaults to 'index.html'
+ @href is not modified unless it is root-relative and obviously specifies a
+ directory (ends in '/'), in which case /index.html will be added
+ If @id's are present, site.xml entries with a matching @tab will be in that tab.
+
+ Tabs can be embedded to a depth of two. The second level of tabs will only
+ be displayed when their parent tab is selected.
+ -->
+
+ <tab id="" label="Home" dir="" indexfile="index.html"/>
+
+
+ <!-- tab id="samples" label="Samples" dir="samples" indexfile="sample.html">
+ <tab id="samples-index" label="Index" dir="samples" indexfile="index.html"/>
+ <tab id="samples-sample2" label="Sample2" dir="samples" indexfile="static.html"/>
+ </tab -->
+ <!-- tab label="Apache XML Projects" href="http://xml.apache.org">
+ <tab label="Forrest" href="http://forrest.apache.org"/>
+ <tab label="Xerces" href="http://xml.apache.org/xerces"/>
+ </tab -->
+ <!-- tab id="plugins" label="Plugins" dir="pluginDocs/plugins_0_70" indexfile="index.html"/ -->
+ <!-- Add new tabs here, eg:
+ <tab label="How-Tos" dir="community/howto/"/>
+ <tab label="XML Site" dir="xml-site/"/>
+ -->
+
+</tabs>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
+"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
+
+<!-- OASIS XML Catalog for Forrest Documents -->
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+ prefer="public">
+
+<!-- Download -->
+<public publicId="-//Acme//DTD Hello Document V1.0//EN"
+ uri="hello-v10.dtd"/>
+
+</catalog>
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!-- ===================================================================
+
+ Apache Hello DTD (Version 1.0)
+
+PURPOSE:
+
+
+TYPICAL INVOCATION:
+
+ <!DOCTYPE greeting PUBLIC
+ "-//Acme//DTD Hello Document Vx.y//EN"
+ "hello.dtd">
+
+ where
+
+ x := major version
+ y := minor version
+
+FIXME:
+
+CHANGE HISTORY:
+[Version 1.0]
+ 20041009 Initial version. (JJP)
+
+==================================================================== -->
+
+<!-- =============================================================== -->
+<!-- Greeting type element -->
+<!-- =============================================================== -->
+
+<!ELEMENT greeting (#PCDATA)>
+
+<!-- =============================================================== -->
+<!-- End of DTD -->
+<!-- =============================================================== -->
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--+
+ | Transforms Hello document format.
+ +-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:template match="/">
+ <document>
+ <header>
+ <title>
+ <xsl:value-of select="greeting"/>
+ </title>
+ </header>
+ <body>
+ <xsl:value-of select="greeting"/>
+ </body>
+ </document>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2005 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+
+ <map:components>
+ <map:actions>
+ <map:action logger="sitemap.action.sourcetype" name="sourcetype" src="org.apache.forrest.sourcetype.SourceTypeAction">
+ <sourcetype name="hello-v1.0">
+ <document-declaration public-id="-//Acme//DTD Hello Document V1.0//EN" />
+ </sourcetype>
+ </map:action>
+ </map:actions>
+
+ <map:selectors default="parameter">
+ <map:selector logger="sitemap.selector.parameter" name="parameter" src="org.apache.cocoon.selection.ParameterSelector" />
+ </map:selectors>
+ </map:components>
+
+ <map:resources>
+ <map:resource name="transform-to-document">
+ <map:act type="sourcetype" src="{src}">
+ <map:select type="parameter">
+ <map:parameter name="parameter-selector-test" value="{sourcetype}" />
+
+ <map:when test="hello-v1.0">
+ <map:generate src="{project:content.xdocs}{../../1}.xml" />
+ <map:transform src="{project:resources.stylesheets}/hello2document.xsl" />
+ <map:serialize type="xml-document"/>
+ </map:when>
+ </map:select>
+ </map:act>
+ </map:resource>
+ </map:resources>
+
+ <map:pipelines>
+ <map:pipeline>
+ <map:match pattern="old_site/*.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{1}.html">
+ <map:read src="{project:content}{1}.html" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ </map:select>
+ </map:match>
+
+
+ <map:match pattern="installs/**">
+ <map:read src="../../../lib/wamblee/{1}"/>
+ </map:match>
+
+ <map:match pattern="**.xml">
+ <map:call resource="transform-to-document">
+ <map:parameter name="src" value="{project:content.xdocs}{1}.xml" />
+ </map:call>
+ </map:match>
+ </map:pipeline>
+ </map:pipelines>
+</map:sitemap>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2005 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+Skin configuration file. This file contains details of your project,
+which will be used to configure the chosen Forrest skin.
+-->
+
+<!DOCTYPE skinconfig PUBLIC "-//APACHE//DTD Skin Configuration V0.7-1//EN" "http://forrest.apache.org/dtd/skinconfig-v07-1.dtd">
+<skinconfig>
+ <!-- To enable lucene search add provider="lucene" (default is google).
+ Add box-location="alt" to move the search box to an alternate location
+ (if the skin supports it) and box-location="all" to show it in all
+ available locations on the page. Remove the <search> element to show
+ no search box. @domain will enable sitesearch for the specific domain with google.
+ In other words google will search the @domain for the query string.
+ -->
+ <search name="MyProject" domain="mydomain" provider="google"/>
+
+ <!-- Disable the print link? If enabled, invalid HTML 4.0.1 -->
+ <disable-print-link>true</disable-print-link>
+ <!-- Disable the PDF link? -->
+ <disable-pdf-link>false</disable-pdf-link>
+ <!-- Disable the POD link? -->
+ <disable-pod-link>true</disable-pod-link>
+ <!-- Disable the Text link? FIXME: NOT YET IMPLEMENETED. -->
+ <disable-txt-link>true</disable-txt-link>
+ <!-- Disable the xml source link? -->
+ <!-- The xml source link makes it possible to access the xml rendition
+ of the source frim the html page, and to have it generated statically.
+ This can be used to enable other sites and services to reuse the
+ xml format for their uses. Keep this disabled if you don't want other
+ sites to easily reuse your pages.-->
+ <disable-xml-link>true</disable-xml-link>
+
+ <!-- Disable navigation icons on all external links? -->
+ <disable-external-link-image>true</disable-external-link-image>
+
+ <!-- Disable w3c compliance links?
+ Use e.g. align="center" to move the compliance links logos to
+ an alternate location default is left.
+ (if the skin supports it) -->
+ <disable-compliance-links>false</disable-compliance-links>
+
+ <!-- Render mailto: links unrecognisable by spam harvesters? -->
+ <obfuscate-mail-links>true</obfuscate-mail-links>
+ <obfuscate-mail-value>.at.</obfuscate-mail-value>
+
+ <!-- Disable the javascript facility to change the font size -->
+ <disable-font-script>true</disable-font-script>
+
+ <!-- mandatory project logo
+ default skin: renders it at the top -->
+ <project-name>KiSS Crawler</project-name>
+ <project-description>Automatic recording for KiSS harddisk recorders</project-description>
+ <project-url>http://kiss.wamblee.org</project-url>
+ <project-logo>images/project.png</project-logo>
+ <!-- Alternative static image:
+ <project-logo>images/project-logo.gif</project-logo> -->
+
+ <!-- optional group logo
+ default skin: renders it at the top-left corner -->
+ <group-name>wamblee.org</group-name>
+ <group-description></group-description>
+ <group-url>http://wamblee.org</group-url>
+ <group-logo>images/group.png</group-logo>
+ <!-- Alternative static image:
+ <group-logo>images/group-logo.gif</group-logo> -->
+
+ <!-- optional host logo (e.g. sourceforge logo)
+ default skin: renders it at the bottom-left corner -->
+ <host-url></host-url>
+ <host-logo></host-logo>
+
+ <!-- relative url of a favicon file, normally favicon.ico -->
+ <favicon-url></favicon-url>
+
+ <!-- The following are used to construct a copyright statement -->
+ <year>2006</year>
+ <vendor>wamblee.org</vendor>
+ <!-- The optional copyright-link URL will be used as a link in the
+ copyright statement
+ <copyright-link>http://www.apache.org/licenses/</copyright-link>
+ -->
+
+ <!-- Some skins use this to form a 'breadcrumb trail' of links.
+ Use location="alt" to move the trail to an alternate location
+ (if the skin supports it).
+ Omit the location attribute to display the trail in the default location.
+ Use location="none" to not display the trail (if the skin supports it).
+ For some skins just set the attributes to blank.
+
+ NOTE: If a breadcrumb entry points at a local file the href must
+ be complete, that is it must point to the file itself, not to a
+ directory.
+ -->
+ <trail>
+ <link1 name="wamblee.org" href="http://wamblee.org/"/>
+ <link2 name="utils" href="http://wamblee.org/utils"/>
+ <link3 name="KiSS crawler" href="http://kiss.wamblee.org"/>
+ </trail>
+
+ <!-- Configure the TOC, i.e. the Table of Contents.
+ @max-depth
+ how many "section" levels need to be included in the
+ generated Table of Contents (TOC).
+ @min-sections
+ Minimum required to create a TOC.
+ @location ("page","menu","page,menu", "none")
+ Where to show the TOC.
+ -->
+ <toc max-depth="2" min-sections="1" location="page"/>
+
+ <!-- Heading types can be clean|underlined|boxed -->
+ <headings type="boxed"/>
+
+ <!-- The optional feedback element will be used to construct a
+ feedback link in the footer with the page pathname appended:
+ <a href="@href">{@to}</a>
+ -->
+ <feedback to="erik@wamblee.org"
+ href="mailto:erik@wamblee.org?subject=Feedback " >
+ Send feedback about the website to:
+ </feedback>
+
+ <!-- Optional message of the day (MOTD).
+ Note: This is only implemented in the pelt skin.
+ If the optional <motd> element is used, then messages will be appended
+ depending on the URI string pattern.
+ motd-option : Specifies a pattern to match and provides small text content.
+ motd-title : This text will be added in brackets after the <html><title>
+ motd-page : This text will be added in a panel on the face of the page,
+ with the "motd-page-url" being the hyperlink "More".
+ Values for the "location" attribute are:
+ page : on the face of the page, e.g. in the spare space of the toc
+ alt : at the bottom of the left-hand navigation panel
+ both : both
+ -->
+<!--
+ <motd>
+ <motd-option pattern="docs_0_80">
+ <motd-title>v0.8-dev</motd-title>
+ <motd-page location="both">
+ This is documentation for development version v0.8
+ </motd-page>
+ <motd-page-url>/versions/index.html</motd-page-url>
+ </motd-option>
+ <motd-option pattern="docs_0_70">
+ <motd-title>v0.7</motd-title>
+ <motd-page location="both">
+ This is documentation for current version v0.7
+ </motd-page>
+ <motd-page-url>/versions/index.html</motd-page-url>
+ </motd-option>
+ </motd>
+-->
+
+ <!--
+ extra-css - here you can define custom css-elements that are
+ A) overriding the fallback elements or
+ B) adding the css definition from new elements that you may have
+ used in your documentation.
+ -->
+ <extra-css>
+ <!--Example of reason B:
+ To define the css definition of a new element that you may have used
+ in the class attribute of a <p> node.
+ e.g. <p class="quote"/>
+ -->
+ p.quote {
+ margin-left: 2em;
+ padding: .5em;
+ background-color: #f0f0f0;
+ font-family: monospace;
+ }
+ <!--Example:
+ To override the colours of links only in the footer.
+ -->
+ #footer a { color: #0F3660; }
+ #footer a:visited { color: #009999; }
+ </extra-css>
+
+ <colors>
+ <!-- These values are used for the generated CSS files.
+ They essentially "override" the default colors defined in the chosen skin.
+ There are four duplicate "groups" of colors below, denoted by comments:
+ Color group: Forrest, Krysalis, Collabnet, and Lenya using Pelt.
+ They are provided for example only. To customize the colors of any skin,
+ uncomment one of these groups of color elements and change the values
+ of the particular color elements that you wish to change.
+ Note that by default, all color groups are commented-out which means that
+ the default colors provided by the skin are being used.
+ -->
+
+ <!-- Color group: Forrest: example colors similar to forrest.apache.org
+ Some of the element names are obscure, so comments are added to show how
+ the "pelt" skin uses them, other skins might use these elements in a different way.
+ Tip: temporarily change the value of an element to red (#ff0000) and see the effect.
+ pelt: breadtrail: the strip at the top of the page and the second strip under the tabs
+ pelt: header: top strip containing project and group logos
+ pelt: heading|subheading: section headings within the content
+ pelt: navstrip: the strip under the tabs which contains the published date
+ pelt: menu: the left-hand navigation panel
+ pelt: toolbox: the selected menu item
+ pelt: searchbox: the background of the searchbox
+ pelt: border: line border around selected menu item
+ pelt: body: any remaining parts, e.g. the bottom of the page
+ pelt: footer: the second from bottom strip containing credit logos and published date
+ pelt: feedback: the optional bottom strip containing feedback link
+ -->
+<!--
+ <color name="breadtrail" value="#cedfef" font="#0F3660" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="header" value="#294563"/>
+ <color name="tab-selected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="tab-unselected" value="#b5c7e7" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="subtab-selected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="subtab-unselected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="heading" value="#294563"/>
+ <color name="subheading" value="#4a6d8c"/>
+ <color name="published" value="#4C6C8F" font="#FFFFFF"/>
+ <color name="feedback" value="#4C6C8F" font="#FFFFFF" align="center"/>
+ <color name="navstrip" value="#4a6d8c" font="#ffffff" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="menu" value="#4a6d8c" font="#cedfef" link="#ffffff" vlink="#ffffff" hlink="#ffcf00"/>
+ <color name="toolbox" value="#4a6d8c"/>
+ <color name="border" value="#294563"/>
+ <color name="dialog" value="#4a6d8c"/>
+ <color name="searchbox" value="#4a6d8c" font="#000000"/>
+ <color name="body" value="#ffffff" link="#0F3660" vlink="#009999" hlink="#000066"/>
+ <color name="table" value="#7099C5"/>
+ <color name="table-cell" value="#f0f0ff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#CFDCED"/>
+ <color name="footer" value="#cedfef"/>
+-->
+
+ <!-- Color group: Krysalis -->
+<!--
+ <color name="header" value="#FFFFFF"/>
+
+ <color name="tab-selected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="tab-unselected" value="#F7F7F7" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-selected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+
+ <color name="heading" value="#a5b6c6"/>
+ <color name="subheading" value="#CFDCED"/>
+
+ <color name="navstrip" value="#CFDCED" font="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="toolbox" value="#a5b6c6"/>
+ <color name="border" value="#a5b6c6"/>
+
+ <color name="menu" value="#F7F7F7" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="dialog" value="#F7F7F7"/>
+
+ <color name="body" value="#ffffff" link="#0F3660" vlink="#009999" hlink="#000066"/>
+
+ <color name="table" value="#a5b6c6"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#a5b6c6"/>
+
+ <color name="footer" value="#a5b6c6"/>
+-->
+
+ <!-- Color group: Collabnet -->
+<!--
+ <color name="header" value="#003366"/>
+
+ <color name="tab-selected" value="#dddddd" link="#555555" vlink="#555555" hlink="#555555"/>
+ <color name="tab-unselected" value="#999999" link="#ffffff" vlink="#ffffff" hlink="#ffffff"/>
+ <color name="subtab-selected" value="#cccccc" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#cccccc" link="#555555" vlink="#555555" hlink="#555555"/>
+
+ <color name="heading" value="#003366"/>
+ <color name="subheading" value="#888888"/>
+
+ <color name="navstrip" value="#dddddd" font="#555555"/>
+ <color name="toolbox" value="#dddddd" font="#555555"/>
+ <color name="border" value="#999999"/>
+
+ <color name="menu" value="#ffffff"/>
+ <color name="dialog" value="#eeeeee"/>
+
+ <color name="body" value="#ffffff"/>
+
+ <color name="table" value="#ccc"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#003366"/>
+
+ <color name="footer" value="#ffffff"/>
+-->
+ <!-- Color group: Lenya using pelt-->
+<!--
+
+ <color name="header" value="#ffffff"/>
+
+ <color name="tab-selected" value="#E5E4D9" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="tab-unselected" value="#F5F4E9" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-selected" value="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#E5E4D9" link="#000000" vlink="#000000" hlink="#000000"/>
+
+ <color name="heading" value="#E5E4D9"/>
+ <color name="subheading" value="#000000"/>
+ <color name="published" value="#000000"/>
+ <color name="navstrip" value="#E5E4D9" font="#000000"/>
+ <color name="toolbox" value="#CFDCED" font="#000000"/>
+ <color name="border" value="#999999"/>
+
+ <color name="menu" value="#E5E4D9" font="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="dialog" value="#CFDCED"/>
+ <color name="body" value="#ffffff" />
+
+ <color name="table" value="#ccc"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#003366"/>
+
+ <color name="footer" value="#E5E4D9"/>
+-->
+ </colors>
+
+ <!-- Settings specific to PDF output. -->
+ <pdf>
+ <!--
+ Supported page sizes are a0, a1, a2, a3, a4, a5, executive,
+ folio, legal, ledger, letter, quarto, tabloid (default letter).
+ Supported page orientations are portrait, landscape (default
+ portrait).
+ Supported text alignments are left, right, justify (default left).
+ -->
+ <page size="letter" orientation="portrait" text-align="left"/>
+
+ <!--
+ Pattern of the page numbering in the footer - Default is "Page x".
+ first occurrence of '1' digit represents the current page number,
+ second occurrence of '1' digit represents the total page number,
+ anything else is considered as the static part of the numbering pattern.
+ Examples : x is the current page number, y the total page number.
+ <page-numbering-format>none</page-numbering-format> Do not displays the page numbering
+ <page-numbering-format>1</page-numbering-format> Displays "x"
+ <page-numbering-format>p1.</page-numbering-format> Displays "px."
+ <page-numbering-format>Page 1/1</page-numbering-format> Displays "Page x/y"
+ <page-numbering-format>(1-1)</page-numbering-format> Displays "(x-y)"
+ -->
+ <page-numbering-format>Page 1</page-numbering-format>
+
+ <!--
+ Margins can be specified for top, bottom, inner, and outer
+ edges. If double-sided="false", the inner edge is always left
+ and the outer is always right. If double-sided="true", the
+ inner edge will be left on odd pages, right on even pages,
+ the outer edge vice versa.
+ Specified below are the default settings.
+ -->
+ <margins double-sided="false">
+ <top>1in</top>
+ <bottom>1in</bottom>
+ <inner>1.25in</inner>
+ <outer>1in</outer>
+ </margins>
+
+ <!--
+ Print the URL text next to all links going outside the file
+ -->
+ <show-external-urls>false</show-external-urls>
+
+ <!--
+ Disable the copyright footer on each page of the PDF.
+ A footer is composed for each page. By default, a "credit" with role=pdf
+ will be used, as explained below. Otherwise a copyright statement
+ will be generated. This latter can be disabled.
+ -->
+ <disable-copyright-footer>false</disable-copyright-footer>
+ </pdf>
+
+ <!--
+ Credits are typically rendered as a set of small clickable
+ images in the page footer.
+
+ Use box-location="alt" to move the credits to an alternate location
+ (if the skin supports it).
+
+ For example, pelt skin:
+ - box-location="alt" will place the logo at the end of the
+ left-hand coloured menu panel.
+ - box-location="alt2" will place them underneath that panel
+ in the left-hand whitespace.
+ - Otherwise they are placed next to the compatibility icons
+ at the bottom of the screen.
+
+ Comment out the whole <credit>-element if you want no credits in the
+ web pages
+ -->
+ <credits>
+ <credit box-location="alt">
+ <name>Built with Apache Forrest</name>
+ <url>http://forrest.apache.org/</url>
+ <image>images/built-with-forrest-button.png</image>
+ <width>88</width>
+ <height>31</height>
+ </credit>
+ <!-- A credit with @role="pdf" will be used to compose a footer
+ for each page in the PDF, using either "name" or "url" or both.
+ -->
+ <!--
+ <credit role="pdf">
+ <name>Built with Apache Forrest</name>
+ <url>http://forrest.apache.org/</url>
+ </credit>
+ -->
+ </credits>
+
+</skinconfig>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!--
+ This catalog is used so displays how the language name
+ is named by their speakers.
+-->
+<catalogue >
+ <message key="en">English</message>
+ <message key="es">Espanol</message>
+ <message key="it">Italiano</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="en">English</message>
+ <message key="es">Spanish</message>
+ <message key="nl">Dutch</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="en">Inglés</message>
+ <message key="es">Español</message>
+ <message key="nl">Holandés</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="About">About</message>
+ <message key="Index">Index</message>
+ <message key="Changes">Changes</message>
+ <message key="Todo">Todo</message>
+ <message key="Samples">Samples</message>
+ <message key="Apache document">Apache document</message>
+ <message key="Static content">Static content</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki page</message>
+ <message key="ihtml page">Ihtml page</message>
+ <message key="ehtml page">Ehtml page</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">XSP page</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="af">
+ <message key="About">Aangaande</message>
+ <message key="Index">Inhoud</message>
+ <message key="Changes">Veranderinge</message>
+ <message key="Todo">Om te doen</message>
+ <message key="Samples">Voorbeelde</message>
+ <message key="Apache document">Apache dokument</message>
+ <message key="Static content">Statise Inhoud</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki bladsy</message>
+ <message key="ihtml page">Ihtml bladsy</message>
+ <message key="ehtml page">Ehtml bladsy</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Vereenvoudigde Docbook</message>
+ <message key="XSP page">XSP bladsy</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="de">
+ <message key="About">Über</message>
+ <message key="Index">Index</message>
+ <message key="Changes">Änderungen </message>
+ <message key="Todo">Todo</message>
+ <message key="Samples">Beispiele</message>
+ <message key="Apache document">Apache Dokumentationsseite</message>
+ <message key="Static content">Statischer Inhalt</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki Seite</message>
+ <message key="ihtml page">ihtml Seite</message>
+ <message key="ehtml page">ehtml Seite</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Vereinfachte Docbook</message>
+ <message key="XSP page">XSP Seite</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="About">Acerca de</message>
+ <message key="Index">Indice</message>
+ <message key="Changes">Cambios</message>
+ <message key="Todo">Tareas pendientes</message>
+ <message key="Samples">Ejemplos</message>
+ <message key="Apache document">Documento Apache</message>
+ <message key="Static content">Contenido Estático</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Página Wiki</message>
+ <message key="ihtml page">Página ihtml</message>
+ <message key="ehtml page">Página ehtml</message>
+ <message key="FAQ">Preguntas Frecuentes</message>
+ <message key="Simplifed Docbook">Página Simplifed Docbook</message>
+ <message key="XSP page">Página XSP</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="it">
+ <message key="About">Riguardo a</message>
+ <message key="Index">Indice</message>
+ <message key="Changes">Cambiamenti</message>
+ <message key="Todo">Cose da fare</message>
+ <message key="Samples">Esempi</message>
+ <message key="Apache document">Apache document</message>
+ <message key="Static content">Contenuto Statico</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Pagina Wiki</message>
+ <message key="ihtml page">Pagina ihtml</message>
+ <message key="ehtml page">Pagina ehtml</message>
+ <message key="FAQ">Domande frequenti</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">Pagina XSP</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="no">
+ <message key="About">Om</message>
+ <message key="Index">Indeks</message>
+ <message key="Changes">Endringer</message>
+ <message key="Todo">Oppgave liste</message>
+ <message key="Samples">Eksempler</message>
+ <message key="Apache document">Apache Dokument</message>
+ <message key="Static content">Statisk innhold</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki side</message>
+ <message key="ihtml page">ihtml side</message>
+ <message key="ehtml page">ehtml side</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">XSP side</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="ru">
+ <message key="About">О проекте</message>
+ <message key="Index">Содержание</message>
+ <message key="Changes">Изменения</message>
+ <message key="Todo">План</message>
+ <message key="Samples">Примеры</message>
+ <message key="Apache document">Страница документа Apache</message>
+ <message key="Static content">Статическое содержание</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Страница Wiki</message>
+ <message key="ihtml page">Страница ihtml</message>
+ <message key="ehtml page">Страница ehtml</message>
+ <message key="FAQ">Вопросы/Ответы</message>
+ <message key="Simplifed Docbook">Docbook страница</message>
+ <message key="XSP page">XSP страница</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="sk">
+ <message key="About">O programe</message>
+ <message key="Index">Zoznám</message>
+ <message key="Changes">Zmeny</message>
+ <message key="Todo">Úlohy</message>
+ <message key="Samples">Príklady</message>
+ <message key="Apache document">Apache Document</message>
+ <message key="Static content">Statický Obsah</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki stránka</message>
+ <message key="ihtml page">ihtml stránka</message>
+ <message key="ehtml page">ehtml stránka</message>
+ <message key="FAQ">Casté Otázky</message>
+ <message key="Simplifed Docbook">Simplifed Docbook stránka</message>
+ <message key="XSP page">XSP stránka</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="Home">Home</message>
+ <message key="Samples">Samples</message>
+ <message key="Apache XML Projects">Apache XML Projects</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="Home">Inicio</message>
+ <message key="Samples">Ejemplos</message>
+ <message key="Apache XML Projects">Projectos XML Apache</message>
+</catalogue>
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+##############
+# Properties used by forrest.build.xml for building the website
+# These are the defaults, un-comment them only if you need to change them.
+##############
+
+# Prints out a summary of Forrest settings for this project
+#forrest.echo=true
+
+# Project name (used to name .war file)
+#project.name=my-project
+
+# Specifies name of Forrest skin to use
+# See list at http://forrest.apache.org/docs/skins.html
+#project.skin=pelt
+
+# Descriptors for plugins and skins
+# comma separated list, file:// is supported
+#forrest.skins.descriptors=http://forrest.apache.org/skins/skins.xml,file:///c:/myskins/skins.xml
+#forrest.plugins.descriptors=http://forrest.apache.org/plugins/plugins.xml,http://forrest.apache.org/plugins/whiteboard-plugins.xml
+
+##############
+# behavioural properties
+#project.menu-scheme=tab_attributes
+#project.menu-scheme=directories
+
+##############
+# layout properties
+
+# Properties that can be set to override the default locations
+#
+# Parent properties must be set. This usually means uncommenting
+# project.content-dir if any other property using it is uncommented
+
+project.build-dir=${project.home}/target/forrest
+
+#project.status=status.xml
+project.content-dir=docs
+#project.raw-content-dir=${project.content-dir}/content
+#project.conf-dir=${project.content-dir}/conf
+#project.sitemap-dir=${project.content-dir}
+#project.xdocs-dir=${project.content-dir}/content/xdocs
+#project.resources-dir=${project.content-dir}/resources
+#project.stylesheets-dir=${project.resources-dir}/stylesheets
+#project.images-dir=${project.resources-dir}/images
+#project.schema-dir=${project.resources-dir}/schema
+#project.skins-dir=${project.content-dir}/skins
+#project.skinconf=${project.content-dir}/skinconf.xml
+#project.lib-dir=${project.content-dir}/lib
+#project.classes-dir=${project.content-dir}/classes
+#project.translations-dir=${project.content-dir}/translations
+
+##############
+# validation properties
+
+# This set of properties determine if validation is performed
+# Values are inherited unless overridden.
+# e.g. if forrest.validate=false then all others are false unless set to true.
+#forrest.validate=true
+#forrest.validate.xdocs=${forrest.validate}
+#forrest.validate.skinconf=${forrest.validate}
+#forrest.validate.sitemap=${forrest.validate}
+#forrest.validate.stylesheets=${forrest.validate}
+#forrest.validate.skins=${forrest.validate}
+#forrest.validate.skins.stylesheets=${forrest.validate.skins}
+
+# *.failonerror=(true|false) - stop when an XML file is invalid
+#forrest.validate.failonerror=true
+
+# *.excludes=(pattern) - comma-separated list of path patterns to not validate
+# e.g.
+#forrest.validate.xdocs.excludes=samples/subdir/**, samples/faq.xml
+#forrest.validate.xdocs.excludes=
+
+
+##############
+# General Forrest properties
+
+# The URL to start crawling from
+#project.start-uri=linkmap.html
+
+# Set logging level for messages printed to the console
+# (DEBUG, INFO, WARN, ERROR, FATAL_ERROR)
+#project.debuglevel=ERROR
+
+# Max memory to allocate to Java
+#forrest.maxmemory=64m
+
+# Any other arguments to pass to the JVM. For example, to run on an X-less
+# server, set to -Djava.awt.headless=true
+#forrest.jvmargs=
+
+# The bugtracking URL - the issue number will be appended
+#project.bugtracking-url=http://issues.apache.org/bugzilla/show_bug.cgi?id=
+#project.bugtracking-url=http://issues.apache.org/jira/browse/
+
+# The issues list as rss
+#project.issues-rss-url=
+
+#I18n Property. Based on the locale request for the browser.
+#If you want to use it for static site then modify the JVM system.language
+# and run once per language
+#project.i18n=true
+
+# The names of plugins that are required to build the project
+# comma separated list (no spaces)
+# You can request a specific version by appending "-VERSION" to the end of
+# the plugin name. If you exclude a version number the latest released version
+# will be used, however, be aware that this may be a development version. In
+# a production environment it is recomended that you specify a known working
+# version.
+# Run "forrest available-plugins" for a list of plug-ins currently available
+project.required.plugins=org.apache.forrest.plugin.output.pdf
+
+# Proxy configuration
+# proxy.host=
+# proxy.port=
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-kiss</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org KiSS crawler</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-basic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-email</groupId>
+ <artifactId>commons-email</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+/**
+ * Abstract visitor of the tv guide with default looping behavior.
+ */
+public abstract class AbstractVisitor implements Visitor {
+
+ /**
+ * Constructs the visitor.
+ *
+ */
+ protected AbstractVisitor() {
+ // Empty
+ }
+
+ /**
+ * Visits the channel by visiting all programs of the channel.
+ *
+ * @param aChannel
+ * Channel to visit.
+ */
+ public void visitChannel(Channel aChannel) {
+ for (Program program : aChannel.getPrograms()) {
+ program.accept(this);
+ }
+ }
+
+ /**
+ * Visits the TV guide by visiting all channels of the guide.
+ *
+ * @param aGuide
+ * TV guide to visit.
+ */
+ public void visitTvGuide(TVGuide aGuide) {
+ for (Channel channel : aGuide.getChannels()) {
+ channel.accept(this);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Represents the programme for a tv channel.
+ */
+public class Channel {
+
+ /**
+ * TV channel name.
+ */
+ private String _name;
+
+ /**
+ * List of programs in chronological order.
+ */
+ private List<Program> _programs;
+
+ /**
+ * Constructs the channel.
+ * @param aName Channel name.
+ * @param aPrograms Programs.
+ */
+ public Channel(String aName, List<Program> aPrograms) {
+ _name = aName;
+ _programs = aPrograms;
+ }
+
+ /**
+ * Gets the channel name.
+ * @return channel name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Gets the list of program.
+ * @return Programs.
+ */
+ public List<Program> getPrograms() {
+ return Collections.unmodifiableList(_programs);
+ }
+
+ /**
+ * Accepts a visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitChannel(this);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+
+/**
+ * Visitor which determines the interesting programs in the TV guide.
+ */
+public class MatchVisitor extends AbstractVisitor {
+
+ /**
+ * Criterion that determines which programs are interesting.
+ */
+ private Condition<Program> _matcher;
+
+ /**
+ * List of interesting programs.
+ */
+ private List<Program> _programs;
+
+ /**
+ * Constructs the visitor.
+ * @param aMatcher Condition describing interesting programs.
+ */
+ public MatchVisitor(Condition<Program> aMatcher) {
+ _matcher = aMatcher;
+ _programs = new ArrayList<Program>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.Visitor#visitProgram(org.wamblee.crawler.kiss.Program)
+ */
+ public void visitProgram(Program aProgram) {
+ if (_matcher.matches(aProgram)) {
+ _programs.add(aProgram);
+ }
+ }
+
+ /**
+ * Gets the list of interesting programs. To be called after applying
+ * the visitor on a tv guide.
+ * @return List of interesting programs.
+ */
+ public List<Program> getMatches() {
+ return _programs;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.io.PrintStream;
+
+
+/**
+ * Print visitor for pretty printing the TV guide.
+ */
+public class PrintVisitor extends AbstractVisitor {
+
+ /**
+ * Stream to print the guide on.
+ */
+ private PrintStream _stream;
+
+ /**
+ * Constructs the print visitor.
+ * @param aStream Stream to print on.
+ */
+ public PrintVisitor(PrintStream aStream) {
+ _stream = aStream;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.Visitor#visitProgram(org.wamblee.crawler.kiss.Program)
+ */
+ public void visitProgram(Program aProgram) {
+ _stream.println(" " + aProgram.toString());
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.AbstractVisitor#visitChannel(org.wamblee.crawler.kiss.Channel)
+ */
+ @Override
+ public void visitChannel(Channel aChannel) {
+ _stream.println(aChannel.getName());
+ super.visitChannel(aChannel);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Comparator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.kiss.main.SystemProperties;
+
+/**
+ * Represents a television program.
+ */
+public class Program {
+
+ private static final String ELEM_PROGRAM = "program";
+
+ private static final String ELEM_NAME = "name";
+
+ private static final String ELEM_KEYWORDS = "keywords";
+
+ private static final String ELEM_DESCRIPTION = "description";
+
+ private static final String ELEM_CHANNEL = "channel";
+
+ private static final String ELEM_INTERVAL = "interval";
+
+ private static final String ELEM_END_TIME = "end";
+
+ private static final String ELEM_BEGIN_TIME = "begin";
+
+ /**
+ * Lexicographical comparison of programs based on (time, title, channel).
+ *
+ */
+ public static class TimeComparator implements Comparator<Program> {
+
+ /**
+ * Lexicographical comparison based on start time, program name, and
+ * channel.
+ *
+ * @param aProgram1
+ * First program.
+ * @param aProgram2
+ * Second program.
+ * @return See {@link Comparator#compare(T, T)}
+ */
+ public int compare(Program aProgram1, Program aProgram2) {
+ int value = aProgram1.getInterval().getBegin().compareTo(
+ aProgram2.getInterval().getBegin());
+ if (value != 0) {
+ return value;
+ }
+ value = aProgram1.getName().compareTo(aProgram2.getName());
+ if (value != 0) {
+ return value;
+ }
+ return aProgram1.getChannel().compareTo(aProgram2.getChannel());
+ }
+ }
+
+ private static final Log LOG = LogFactory.getLog(Program.class);
+
+ /**
+ * Name of the record action on the program details page.
+ */
+ private static final String RECORD_ACTION = "record";
+
+ /**
+ * Result of recording a program.
+ *
+ */
+ public enum RecordingResult {
+ /**
+ * Successfully recorded.
+ */
+ OK,
+
+ /**
+ * Already recorded program.
+ */
+ DUPLICATE,
+
+ /**
+ * Recording conflict with another program.
+ */
+ CONFLICT,
+
+ /**
+ * Program occurred in the past.
+ */
+ OLDSHOW,
+
+ /**
+ * Program could not be recorded for technical reasons.
+ */
+ ERROR;
+ };
+
+ /**
+ * Indent string to use for pretty printing.
+ */
+ private static final String INDENT = " ";
+
+ /**
+ * Channel the program is on.
+ */
+ private String _channel;
+
+ /**
+ * Program name.
+ */
+ private String _name;
+
+ /**
+ * Program description.
+ */
+ private String _description;
+
+ /**
+ * Keywords or classification of the program.
+ */
+ private String _keywords;
+
+ /**
+ * Time interval for the program (from/to).
+ */
+ private TimeInterval _interval;
+
+ /**
+ * Action to execute to obtain program information and/or record the
+ * program.
+ */
+ private Action _programInfo;
+
+ /**
+ * Constructs the program.
+ *
+ * @param aChannel
+ * Channel name.
+ * @param aName
+ * Program name.
+ * @param aDescription
+ * Description.
+ * @param aKeywords
+ * Keywords/classification.
+ * @param aInterval
+ * Time interval.
+ * @param aProgramInfo
+ * Action to execute for detailed program information or for
+ * recording the page.
+ */
+ public Program(String aChannel, String aName, String aDescription,
+ String aKeywords, TimeInterval aInterval, Action aProgramInfo) {
+ _channel = aChannel;
+ _name = aName;
+ _description = aDescription;
+ _keywords = aKeywords;
+ _interval = aInterval;
+ _programInfo = aProgramInfo;
+ }
+
+ /**
+ * Gets the channel.
+ *
+ * @return Channel.
+ */
+ public String getChannel() {
+ return _channel;
+ }
+
+ /**
+ * Gets the program name.
+ *
+ * @return Name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Gets the description.
+ *
+ * @return Description.
+ */
+ public String getDescription() {
+ return _description;
+ }
+
+ /**
+ * Gets the keywords/classification.
+ *
+ * @return Keywords/classification
+ */
+ public String getKeywords() {
+ return _keywords;
+ }
+
+ /**
+ * Gets the time interval.
+ *
+ * @return Time interval.
+ */
+ public TimeInterval getInterval() {
+ return _interval;
+ }
+
+ /**
+ * Checks if recording is possible.
+ *
+ * @return True iff recording is possible.
+ */
+ public boolean isRecordingPossible() {
+ try {
+ return _programInfo.execute().getAction(RECORD_ACTION) != null;
+ } catch (PageException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Records the show.
+ *
+ * @return Status describing the result of recording.
+ */
+ public RecordingResult record() {
+ LOG.info("Recording " + this);
+ if (SystemProperties.isRecordDisabled()) {
+ return RecordingResult.OK;
+ }
+ try {
+ Action record = _programInfo.execute().getAction(RECORD_ACTION);
+ if (record == null) {
+ LOG.info(" result: " + RecordingResult.OLDSHOW);
+ return RecordingResult.OLDSHOW;
+ }
+ Page result = record.execute();
+ RecordingResult recordingResult = RecordingResult.valueOf(result
+ .getContent().getText());
+ LOG.info(" result: " + recordingResult);
+ return recordingResult;
+ } catch (PageException e) {
+ LOG.warn("Technical problem recording program: '" + this + "'", e);
+ LOG.info(" result: " + RecordingResult.ERROR);
+ return RecordingResult.ERROR;
+ }
+ }
+
+ /**
+ * Accepts the visitor.
+ *
+ * @param aVisitor
+ * Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitProgram(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return _interval + " - " + _name + " (" + _channel + "/" + _keywords
+ + ")" + "\n"
+ + (INDENT + _description).replaceAll("\n", "\n" + INDENT);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof Program)) {
+ return false;
+ }
+ Program program = (Program) aObject;
+ return getName().equals(program.getName())
+ && _programInfo.equals(program._programInfo);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ /**
+ * Converts program information to XML.
+ *
+ * @return XML representation of program information.
+ */
+ public Element asXml() {
+ DocumentFactory factory = DocumentFactory.getInstance();
+ Element program = factory.createElement(ELEM_PROGRAM);
+ program.addElement(ELEM_NAME).setText(getName());
+ program.addElement(ELEM_DESCRIPTION).setText(getDescription());
+ program.addElement(ELEM_KEYWORDS).setText(getKeywords());
+ program.addElement(ELEM_CHANNEL).setText(getChannel());
+ Element interval = program.addElement(ELEM_INTERVAL);
+ interval.addElement(ELEM_BEGIN_TIME).setText(
+ getInterval().getBegin().toString());
+ interval.addElement(ELEM_END_TIME).setText(
+ getInterval().getEnd().toString());
+ return program;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * The TV guide.
+ */
+public class TVGuide {
+
+ /**
+ * List of channels.
+ */
+ private List<Channel> _channels;
+
+ /**
+ * Constructs the guide.
+ * @param aChannels Channels of the guide.
+ */
+ public TVGuide(List<Channel> aChannels) {
+ _channels = aChannels;
+ }
+
+ /**
+ * Gets the channels.
+ * @return Channels.
+ */
+ public List<Channel> getChannels() {
+ return Collections.unmodifiableList(_channels);
+ }
+
+ /**
+ * Accepts the visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitTvGuide(this);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * TIme at which a program starts or ends.
+ */
+public class Time implements Comparable {
+
+ /**
+ *
+ */
+ private static final int HOURS_PER_DAY = 24;
+
+ /**
+ *
+ */
+ private static final int EARLY_HOUR = 3;
+
+ /**
+ * Number of seconds per minute.
+ */
+ private static final double SECONDS_PER_MINUTE = 60.0;
+
+ /**
+ * Hour of the time.
+ */
+ private int _hour;
+
+ /**
+ * Minute of the hour.
+ */
+ private int _minute;
+
+ /**
+ * Constructs the time.
+ *
+ * @param aHour
+ * Hour.
+ * @param aMinute
+ * Minute.
+ */
+ public Time(int aHour, int aMinute) {
+ _hour = aHour;
+ _minute = aMinute;
+ }
+
+ /**
+ * Gets the hour.
+ *
+ * @return Hour.
+ */
+ public int getHour() {
+ return _hour;
+ }
+
+ /**
+ * Gets te minute.
+ *
+ * @return Minute.
+ */
+ public int getMinute() {
+ return _minute;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ NumberFormat format = new DecimalFormat("00");
+ return format.format(_hour) + ":" + format.format(_minute);
+ }
+
+ /**
+ * Convert time to floating point value. Useful for comparing two times.
+ *
+ * @return Converted value.
+ */
+ float asFloat() {
+ int hour = _hour;
+ // Hack to make sure that programs appearing shortly after midnight are
+ // sorted
+ // after those running during the day.
+ if (hour <= EARLY_HOUR) {
+ hour += HOURS_PER_DAY;
+ }
+ return (float) hour + (float) _minute / (float) SECONDS_PER_MINUTE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof Time)) {
+ return false;
+ }
+ return toString().equals(aObject.toString());
+ }
+
+ /**
+ * Compares based on time.
+ *
+ * @param aObject
+ * Time object to compare to.
+ * @return See {@link Comparable#compareTo(T)}.
+ */
+ public int compareTo(Object aObject) {
+ if (!(aObject instanceof Time)) {
+ throw new IllegalArgumentException("object not an instance of Time");
+ }
+ Time time = (Time) aObject;
+ return new Float(asFloat()).compareTo(new Float(time.asFloat()));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+/**
+ * Time interval.
+ */
+public class TimeInterval {
+
+ /**
+ * Begin time.
+ */
+ private Time _begin;
+
+ /**
+ * End time.
+ */
+ private Time _end;
+
+ /**
+ * Construts the interval.
+ *
+ * @param aBegin
+ * Start time.
+ * @param aEnd
+ * End time.
+ */
+ public TimeInterval(Time aBegin, Time aEnd) {
+ _begin = aBegin;
+ _end = aEnd;
+ }
+
+ /**
+ * Gets the begin time.
+ *
+ * @return Begin time.
+ */
+ public Time getBegin() {
+ return _begin;
+ }
+
+ /**
+ * Gets the end time.
+ *
+ * @return End time.
+ */
+ public Time getEnd() {
+ return _end;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return _begin + " - " + _end;
+ }
+
+ /**
+ * Determines if there is an overlap between the current interval and given
+ * one.
+ *
+ * @param aInterval
+ * Interval to compare with.
+ * @return True iff there is overlap
+ */
+ public boolean overlap(TimeInterval aInterval) {
+
+ if (isUncertain() || aInterval.isUncertain()) {
+ // Optimistic assume there is no overlap if one of the intervals is
+ // uncertain.
+ return false;
+ }
+
+ if (_end.asFloat() <= aInterval._begin.asFloat()
+ || aInterval._end.asFloat() <= _begin.asFloat()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the actual time that the program corresponds to is
+ * uncertain due to the representation of a period of more than 24 hours
+ * using a 24 hour clock.
+ *
+ * @return True iff the interval is uncertain.
+ */
+ boolean isUncertain() {
+ return _begin.asFloat() > _end.asFloat();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)j
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof TimeInterval)) {
+ return false;
+ }
+ return aObject.toString().equals(aObject.toString());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _begin.hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+
+/**
+ * Visitor of the TV guide.
+ */
+public interface Visitor {
+
+ /**
+ * Visits a program.
+ * @param aProgram Program.
+ */
+ void visitProgram(Program aProgram);
+
+ /**
+ * Visits a channel.
+ * @param aChannel Channel.
+ */
+ void visitChannel(Channel aChannel);
+
+ /**
+ * Visits the guide.
+ * @param aGuide Guide.
+ */
+ void visitTvGuide(TVGuide aGuide);
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package contains the object model for the TV guide and the classes for
+searching the TV guide for relevant programs.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action to execute for an interesting program.
+ */
+public class InterestingProgramAction implements ProgramAction {
+
+ /**
+ * Category under which the interesting program is listed.
+ */
+ private String _category;
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCategory
+ * Category of the program. Useful for structuring the output.
+ */
+ public InterestingProgramAction(String aCategory) {
+ _category = aCategory;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramAction#execute(org.wamblee.crawler.kiss.Program,
+ * org.wamblee.crawler.kiss.Report)
+ */
+ public void execute(Program aProgram, ProgramActionExecutor aReport) {
+ if (aProgram.isRecordingPossible()) {
+ aReport.interestingProgram(_category, aProgram);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.mail.MessagingException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.impl.ConfigurationParser;
+import org.wamblee.crawler.impl.CrawlerImpl;
+import org.wamblee.crawler.kiss.guide.Channel;
+import org.wamblee.crawler.kiss.guide.PrintVisitor;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TVGuide;
+import org.wamblee.crawler.kiss.guide.Time;
+import org.wamblee.crawler.kiss.guide.TimeInterval;
+import org.wamblee.crawler.kiss.notification.NotificationException;
+import org.wamblee.crawler.kiss.notification.Notifier;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.xml.ClasspathUriResolver;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * The KiSS crawler for automatic recording of interesting TV shows.
+ *
+ */
+public class KissCrawler {
+
+ private static final Log LOG = LogFactory.getLog(KissCrawler.class);
+
+ /**
+ * Start URL of the electronic programme guide.
+ */
+ private static final String START_URL = "http://epg.kml.kiss-technology.com/login.php";
+
+ /**
+ * Default socket timeout to use.
+ */
+ private static final int SOCKET_TIMEOUT = 10000;
+
+ /**
+ * Regular expression for matching time interval strings in the retrieved
+ * pages.
+ */
+ private static final String TIME_REGEX = "[^0-9]*([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
+
+ /**
+ * Compiled pattern for the time regular expression.
+ */
+ private Pattern _pattern;
+
+ /**
+ * Runs the KiSS crawler.
+ *
+ * @param aArgs
+ * Arguments, currently all ignored because they are hardcoded.
+ * @throws Exception
+ * In case of problems.
+ */
+ public static void main(String[] aArgs) throws Exception {
+ String crawlerConfig = new File(aArgs[0]).getCanonicalPath();
+ String programConfig = new File(aArgs[1]).getCanonicalPath();
+
+ BeanFactory factory = new StandaloneCrawlerBeanFactory();
+ Notifier notifier = factory.find(Notifier.class);
+ new KissCrawler(START_URL, SOCKET_TIMEOUT, crawlerConfig,
+ programConfig, notifier, new Report());
+ }
+
+ /**
+ * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
+ * EPG guide, filters the guide for interesting programs, tries to record
+ * them, and sends a summary mail to the user.
+ *
+ * @param aCrawlerConfig
+ * Configuration file for the crawler.
+ * @param aProgramConfig
+ * Configuration file describing interesting shows.
+ * @param aNotifier
+ * Object used to send notifications of the results.
+ * @param aReport
+ * Report to use.
+ * @throws IOException
+ * In case of problems reading files.
+ * @throws NotificationException
+ * In case notification fails.
+ * @throws PageException
+ * In case of problems retrieving the TV guide.
+ */
+ public KissCrawler(String aCrawlerConfig, String aProgramConfig,
+ Notifier aNotifier, Report aReport) throws IOException,
+ NotificationException, PageException {
+ this(START_URL, SOCKET_TIMEOUT, aCrawlerConfig, aProgramConfig,
+ aNotifier, aReport);
+ }
+
+ /**
+ * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
+ * EPG guide, filters the guide for interesting programs, tries to record
+ * them, and sends a summary mail to the user.
+ *
+ * @param aStartUrl
+ * Start URL of the electronic programme guide.
+ * @param aSocketTimeout
+ * Socket timeout to use.
+ * @param aCrawlerConfig
+ * Configuration file for the crawler.
+ * @param aProgramConfig
+ * Configuration file describing interesting shows.
+ * @param aNotifier
+ * Object used to send notifications of the results.
+ * @param aReport
+ * Report to use.
+ * @throws IOException
+ * In case of problems reading files.
+ * @throws NotificationException
+ * In case notification fails.
+ * @throws PageException
+ * In case of problems retrieving the TV guide.
+ */
+ public KissCrawler(String aStartUrl, int aSocketTimeout,
+ String aCrawlerConfig, String aProgramConfig, Notifier aNotifier,
+ Report aReport) throws IOException, NotificationException,
+ PageException {
+
+ _pattern = Pattern.compile(TIME_REGEX);
+
+ try {
+ HttpClient client = new HttpClient();
+ // client.getHostConfiguration().setProxy("127.0.0.1", 3128);
+ client.getParams().setParameter("http.socket.timeout",
+ SOCKET_TIMEOUT);
+
+ XslTransformer transformer = new XslTransformer(
+ new ClasspathUriResolver());
+
+ Crawler crawler = createCrawler(aCrawlerConfig, client, transformer);
+ InputStream programConfigFile = new FileInputStream(new File(
+ aProgramConfig));
+ ProgramConfigurationParser parser = new ProgramConfigurationParser();
+ parser.parse(programConfigFile);
+ List<ProgramFilter> programFilters = parser.getFilters();
+
+ try {
+ Page page = getStartPage(aStartUrl, crawler, aReport);
+ TVGuide guide = createGuide(page, aReport);
+ PrintVisitor printer = new PrintVisitor(System.out);
+ guide.accept(printer);
+ processResults(programFilters, guide, aNotifier, aReport);
+ } catch (PageException e) {
+ aReport.addMessage("Problem getting TV guide", e);
+ LOG.info("Problem getting TV guide", e);
+ throw e;
+ }
+ aNotifier.send(aReport.asXml());
+ } finally {
+ System.out.println("Crawler finished");
+ }
+ }
+
+ /**
+ * Records interesting shows.
+ *
+ * @param aProgramCondition
+ * Condition determining which shows are interesting.
+ * @param aGuide
+ * Television guide.
+ * @throws MessagingException
+ * In case of problems sending a summary mail.
+ */
+ private void processResults(List<ProgramFilter> aProgramCondition,
+ TVGuide aGuide, Notifier aNotifier, Report aReport) {
+ ProgramActionExecutor executor = new ProgramActionExecutor(aReport);
+ for (ProgramFilter filter : aProgramCondition) {
+ List<Program> programs = filter.apply(aGuide);
+ ProgramAction action = filter.getAction();
+ for (Program program : programs) {
+ action.execute(program, executor);
+ }
+ }
+ executor.commit();
+
+ }
+
+ /**
+ * Creates the crawler.
+ *
+ * @param aCrawlerConfig
+ * Crawler configuration file.
+ * @param aOs
+ * Logging output stream for the crawler.
+ * @param aClient
+ * HTTP Client to use.
+ * @return Crawler.
+ * @throws FileNotFoundException
+ * In case configuration files cannot be found.
+ */
+ private Crawler createCrawler(String aCrawlerConfig, HttpClient aClient,
+ XslTransformer aTransformer) throws FileNotFoundException {
+ ConfigurationParser parser = new ConfigurationParser(aTransformer);
+ InputStream crawlerConfigFile = new FileInputStream(new File(
+ aCrawlerConfig));
+ Configuration config = parser.parse(crawlerConfigFile);
+ Crawler crawler = new CrawlerImpl(aClient, config);
+ return crawler;
+ }
+
+ /**
+ * Gets the start page of the electronic programme guide. This involves
+ * login and navigation to a suitable start page after logging in.
+ *
+ * @param aStartUrl
+ * URL of the electronic programme guide.
+ * @param aCrawler
+ * Crawler to use.
+ * @param aReport
+ * Report to use.
+ * @return Starting page.
+ */
+ private Page getStartPage(String aStartUrl, Crawler aCrawler, Report aReport)
+ throws PageException {
+ try {
+ Page page = aCrawler.getPage(aStartUrl, new NameValuePair[0]);
+ page = page.getAction("login").execute();
+ Action favorites = page.getAction("channels-favorites");
+ if (favorites == null) {
+ String msg = "Channels favorites action not found on start page";
+ throw new PageException(msg);
+ }
+ return favorites.execute();
+ } catch (PageException e) {
+ String msg = "Could not complete login to electronic programme guide.";
+ throw new PageException(msg, e);
+ }
+ }
+
+ /**
+ * Creates the TV guide by web crawling.
+ *
+ * @param aPage
+ * Starting page.
+ * @param aReport
+ * Report to use.
+ * @return TV guide.
+ * @throws PageException
+ * In case of problem getting the tv guide.
+ */
+ private TVGuide createGuide(Page aPage, Report aReport)
+ throws PageException {
+ LOG.info("Obtaining full TV guide");
+ Action[] actions = aPage.getActions();
+ if (actions.length == 0) {
+ LOG.error("No channels found");
+ throw new PageException("No channels found");
+ }
+ List<Channel> channels = new ArrayList<Channel>();
+ for (Action action : actions) {
+ try {
+ LOG.info("Getting channel info for '" + action.getName() + "'");
+ Action tomorrow = action.execute().getAction("tomorrow");
+ if (tomorrow == null) {
+ throw new PageException("Channel summary page for '"
+ + action.getName()
+ + "' does not contain required information");
+ }
+ Channel channel = createChannel(action.getName(), tomorrow
+ .execute(), aReport);
+ channels.add(channel);
+ if (SystemProperties.isDebugMode()) {
+ break; // Only one channel is crawled.
+ }
+ } catch (PageException e) {
+ aReport.addMessage("Could not create channel information for '"
+ + action.getName() + "'");
+ LOG.error("Could not create channel information for '"
+ + action.getName() + "'", e);
+ }
+ }
+ return new TVGuide(channels);
+ }
+
+ /**
+ * Create channel information for a specific channel.
+ *
+ * @param aChannel
+ * Channel name.
+ * @param aPage
+ * Starting page for the channel.
+ * @return Channel.
+ */
+ private Channel createChannel(String aChannel, Page aPage, Report aReport) {
+ LOG.info("Obtaining program for " + aChannel);
+ Action[] programActions = aPage.getActions();
+ List<Program> programs = new ArrayList<Program>();
+ for (Action action : programActions) {
+ String time = action.getContent().element("time").getText().trim();
+ Matcher matcher = _pattern.matcher(time);
+ if (matcher.matches()) {
+ Time begin = new Time(Integer.parseInt(matcher.group(1)),
+ Integer.parseInt(matcher.group(2)));
+ Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
+ .parseInt(matcher.group(4)));
+ TimeInterval interval = new TimeInterval(begin, end);
+ String description = "";
+ String keywords = "";
+
+ if (!SystemProperties.isNoProgramDetailsRequired()) {
+ Element descriptionElem = action.getContent().element(
+ "description");
+ if (descriptionElem == null) {
+ try {
+ Page programInfo = action.execute();
+ description = programInfo.getContent().element(
+ "description").getText().trim();
+ keywords = programInfo.getContent().element(
+ "keywords").getText().trim();
+ } catch (PageException e) {
+ String msg = "Program details could not be determined for '"
+ + action.getName() + "'";
+ aReport.addMessage(msg, e);
+ LOG.warn(msg, e);
+ }
+ } else {
+ description = descriptionElem.getTextTrim();
+ }
+ }
+ Program program = new Program(aChannel, action.getName(),
+ description, keywords, interval, action);
+
+ LOG.info("Got program " + program);
+ programs.add(program);
+ }
+ }
+ return new Channel(aChannel, programs);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action configured for a program.
+ */
+public interface ProgramAction {
+
+ /**
+ * Executes the action.
+ *
+ * @param aProgram
+ * Program to execute the action for.
+ * @param aExecutor
+ * Executor to use.
+ */
+ void execute(Program aProgram, ProgramActionExecutor aExecutor);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TimeInterval;
+import org.wamblee.crawler.kiss.guide.Program.RecordingResult;
+
+/**
+ * Provides execution of actions for programs. Actions use this class to tell
+ * the executor what to do. The executor then decides on exactly what to do and
+ * in what order and makes decisions in case of conflicts.
+ */
+public class ProgramActionExecutor {
+
+ private static final Log LOG = LogFactory
+ .getLog(ProgramActionExecutor.class);
+
+ /**
+ * Map of priority to set of programs.
+ */
+ private Map<Integer, Set<Program>> _showsToRecord;
+
+ /**
+ * Report to use.
+ */
+ private Report _report;
+
+ /**
+ * Constructs the program action executor.
+ *
+ * @param aReport Report to use.
+ */
+ public ProgramActionExecutor(Report aReport) {
+ _showsToRecord = new TreeMap<Integer, Set<Program>>();
+ _report = aReport;
+ }
+
+ /**
+ * Called by an action to indicate the desire to record a program.
+ *
+ * @param aPriority
+ * Priority of the program. Used to resolve conflicts.
+ * @param aProgram
+ * Program to record.
+ */
+ public void recordProgram(int aPriority, Program aProgram) {
+ LOG.info("priority = " + aPriority + ", program: " + aProgram);
+ // Putting -priority into the set makes sure that iteration order
+ // over the priorities will go from higher priority to lower priority.
+ Set<Program> programs = _showsToRecord.get(-aPriority);
+ if (programs == null) {
+ programs = new TreeSet<Program>(new Program.TimeComparator());
+ _showsToRecord.put(-aPriority, programs);
+ }
+ programs.add(aProgram);
+ }
+
+ /**
+ * Called by an action to indicate that a program is interesting.
+ *
+ * @param aCategory
+ * Category of the program.
+ * @param aProgram
+ * Program.
+ */
+ public void interestingProgram(String aCategory, Program aProgram) {
+ LOG.info("category = '" + aCategory + "', program: " + aProgram);
+ _report.interestingProgram(aCategory, aProgram);
+ }
+
+ /**
+ * Makes sure that the actions are performed.
+ */
+ public void commit() {
+ Set<TimeInterval> previouslyRecorded = new HashSet<TimeInterval>();
+ for (Integer priority : _showsToRecord.keySet()) {
+ for (Program program : _showsToRecord.get(priority)) {
+ TimeInterval interval = program.getInterval();
+ if (recordingConflictExists(previouslyRecorded, interval)) {
+ _report.setRecordingResult(RecordingResult.CONFLICT, program);
+ } else {
+ RecordingResult result = program.record();
+ _report.setRecordingResult(result, program);
+ previouslyRecorded.add(interval);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks an interval for overlap with a previously recorded program.
+ *
+ * @param aPreviouslyRecorded
+ * Previously recorded programs.
+ * @param aInterval
+ * Interval.
+ * @return True iff there is a recording conflict.
+ */
+ private boolean recordingConflictExists(
+ Set<TimeInterval> aPreviouslyRecorded, TimeInterval aInterval) {
+ for (TimeInterval recordedInterval : aPreviouslyRecorded) {
+ if (aInterval.overlap(recordedInterval)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.wamblee.conditions.AndCondition;
+import org.wamblee.conditions.Condition;
+import org.wamblee.conditions.PropertyRegexCondition;
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Parse the configuration of desired programs.
+ */
+class ProgramConfigurationParser {
+ private static final int DEFAULT_PRIORITY = 1;
+
+ // Configuration of interesting programs.
+
+ private static final String ELEM_PROGRAM = "program";
+
+ private static final String ELEM_PRIORITY = "priority";
+
+ private static final String ELEM_PATTERN = "match";
+
+ private static final String ELEM_ACTION = "action";
+
+ private static final String ELEM_CATEGORY = "category";
+
+ private static final String ACTION_NOTIFY = "notify";
+
+ private List<ProgramFilter> _filters;
+
+ ProgramConfigurationParser() {
+ _filters = null;
+ }
+
+ /**
+ * Parses the condition used to match the desired programs.
+ *
+ * @param aStream
+ * Input stream to parse from.
+ * @return Condition.
+ */
+ void parse(InputStream aStream) {
+ List<ProgramFilter> filters = new ArrayList<ProgramFilter>();
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(aStream);
+
+ Element root = document.getRootElement();
+
+ for (Iterator i = root.elementIterator(ELEM_PROGRAM); i.hasNext();) {
+ Element program = (Element) i.next();
+
+ Element categoryElem = program.element(ELEM_CATEGORY);
+ String category = "";
+ if (categoryElem != null) {
+ category = categoryElem.getText().trim();
+ }
+
+ Element actionElem = program.element(ELEM_ACTION);
+ int priority = DEFAULT_PRIORITY;
+ String priorityString = program.elementTextTrim(ELEM_PRIORITY);
+ if ( priorityString != null ) {
+ priority = Integer.valueOf(priorityString);
+ }
+ ProgramAction action = new RecordProgramAction(priority);
+ if (actionElem != null) {
+ if (actionElem.getText().equals(ACTION_NOTIFY)) {
+ action = new InterestingProgramAction(category);
+ }
+ }
+
+ List<Condition<Program>> regexConditions = new ArrayList<Condition<Program>>();
+ for (Iterator j = program.elementIterator(ELEM_PATTERN); j
+ .hasNext();) {
+ Element patternElem = (Element) j.next();
+ String fieldName = "name";
+ Attribute fieldAttribute = patternElem.attribute("field");
+ if (fieldAttribute != null) {
+ fieldName = fieldAttribute.getText();
+ }
+ String pattern = ".*(" + patternElem.getText() + ").*";
+ regexConditions.add(new PropertyRegexCondition<Program>(
+ fieldName, pattern, true));
+ }
+ Condition<Program> condition = new AndCondition<Program>(
+ regexConditions);
+ filters.add(new ProgramFilter(condition, action));
+ }
+ _filters = filters;
+ } catch (DocumentException e) {
+ throw new RuntimeException("Error parsing program configuraiton", e);
+ }
+ }
+
+ /**
+ * Returns the list of program filters.
+ *
+ * @return Filter list.
+ */
+ public List<ProgramFilter> getFilters() {
+ return _filters;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+import org.wamblee.crawler.kiss.guide.MatchVisitor;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TVGuide;
+
+/**
+ * Obtains a list of interesting programs from a TV guide and decides what to do
+ * with them.
+ */
+public class ProgramFilter {
+
+ private Condition<Program> _condition;
+
+ private ProgramAction _action;
+
+ /**
+ * Constructs the program filter.
+ * @param aCondition Condition used to find interesting programs.
+ * @param aAction Corresponding action to execute for matching programs.
+ */
+ public ProgramFilter(Condition<Program> aCondition, ProgramAction aAction) {
+ _condition = aCondition;
+ _action = aAction;
+ }
+
+ /**
+ * Gets the action.
+ * @return Action.
+ */
+ public ProgramAction getAction() {
+ return _action;
+ }
+
+ /**
+ * Applies the filter to a TV guide.
+ * @param aGuide TV guide.
+ * @return List of matching programs.
+ */
+ public List<Program> apply(TVGuide aGuide) {
+ MatchVisitor matcher = new MatchVisitor(_condition);
+ aGuide.accept(matcher);
+ return matcher.getMatches();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action to record a program.
+ */
+public class RecordProgramAction implements ProgramAction {
+
+ private int _priority;
+
+ /**
+ * Constructs the action.
+ * @param aPriority Priority of the recording action. Higher values have higher
+ * priority.
+ */
+ public RecordProgramAction(int aPriority) {
+ _priority = aPriority;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramAction#execute(org.wamblee.crawler.kiss.Program,
+ * org.wamblee.crawler.kiss.Report)
+ */
+ public void execute(Program aProgram, ProgramActionExecutor aReport) {
+ aReport.recordProgram(_priority, aProgram);
+ }
+
+}
--- /dev/null
+/*
+ *
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.Program.RecordingResult;
+
+/**
+ * Represents a report on the actions of the crawler.
+ */
+public class Report {
+
+ private static final Log LOG = LogFactory
+ .getLog(Report.class);
+
+ /**
+ * A map of category name to a set of program. Useful for displaying the
+ * output of possibly interesting programs on a per category basis.
+ */
+ private Map<String, Set<Program>> _interestingShows;
+
+ /**
+ * Map or recording result to a set of programs.
+ */
+ private EnumMap<RecordingResult, Set<Program>> _recordings;
+
+ /**
+ * Messages generated while doing all the work.
+ */
+ private List<String> _messages;
+
+ /**
+ * Constructs the report.
+ *
+ */
+ public Report() {
+ _interestingShows = new TreeMap<String, Set<Program>>();
+ _recordings = new EnumMap<RecordingResult, Set<Program>>(
+ RecordingResult.class);
+ for (RecordingResult result : RecordingResult.values()) {
+ _recordings.put(result, new TreeSet<Program>(
+ new Program.TimeComparator()));
+ }
+ _messages = new ArrayList<String>();
+ }
+
+ /**
+ * Adds a message.
+ *
+ * @param aMessage
+ * Message to add.
+ */
+ public void addMessage(String aMessage) {
+ _messages.add(aMessage);
+ }
+
+ /**
+ * Adds a message.
+ *
+ * @param aMessage
+ * Message to add.
+ * @param aException Exception that caused the problem.
+ */
+ public void addMessage(String aMessage, Exception aException) {
+ String msg = aMessage;
+ for (Throwable e = aException; e != null; e = e.getCause()) {
+ msg += ": " + e.getMessage();
+ }
+ addMessage(msg);
+ }
+
+ /**
+ * Called to indicate that a program is interesting.
+ *
+ * @param aCategory
+ * Category of the program.
+ * @param aProgram
+ * Program.
+ */
+ public void interestingProgram(String aCategory, Program aProgram) {
+ LOG.info("category = '" + aCategory + "', program: " + aProgram);
+ Set<Program> programs = _interestingShows.get(aCategory);
+ if (programs == null) {
+ programs = new TreeSet<Program>(new Program.TimeComparator());
+ _interestingShows.put(aCategory, programs);
+ }
+ programs.add(aProgram);
+ }
+
+ /**
+ * Called to specify the result of recording a program.
+ * @param aResult Result.
+ * @param aProgram Program.
+ */
+ public void setRecordingResult(RecordingResult aResult, Program aProgram) {
+ _recordings.get(aResult).add(aProgram);
+ }
+
+
+ /**
+ * Get report as XML.
+ *
+ * @return XML report
+ */
+ public Element asXml() {
+ DocumentFactory factory = DocumentFactory.getInstance();
+ Element report = factory.createElement("report");
+
+ if (_messages.size() > 0) {
+ Element messages = report.addElement("messages");
+ for (String message : _messages) {
+ messages.addElement("message").setText(message);
+ }
+ }
+
+ Set<Program> reportedPrograms = new HashSet<Program>();
+
+ for (RecordingResult result : RecordingResult.values()) {
+ if (_recordings.get(result).size() > 0) {
+ Element recordingResult = report.addElement("recorded")
+ .addAttribute("result", result.toString());
+
+ for (Program program : _recordings.get(result)) {
+ recordingResult.add(program.asXml());
+ reportedPrograms.add(program);
+ }
+ }
+ }
+
+ if (_interestingShows.size() > 0) {
+ Element interesting = report.addElement("interesting");
+ for (String category : _interestingShows.keySet()) {
+ Element categoryElem = interesting;
+ if (category.length() > 0) {
+ categoryElem = interesting.addElement("category");
+ categoryElem.addAttribute("name", category);
+ }
+ for (Program program : _interestingShows.get(category)) {
+ if (!reportedPrograms.contains(program)) {
+ categoryElem.add(program.asXml());
+ } else {
+ LOG.info("Category '" + category + "', program "
+ + program + " already reported");
+ }
+ }
+ if (categoryElem.elements().size() == 0) {
+ // Remove empty category element.
+ LOG
+ .info("Removing element for category '" + category
+ + "'");
+ interesting.remove(categoryElem);
+ }
+ }
+
+ }
+
+ return report;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.general.SpringBeanFactory;
+
+/**
+ * Bean factory used for the standalone crawler application.
+ */
+public class StandaloneCrawlerBeanFactory extends SpringBeanFactory {
+
+ private static final String LOCATOR = "crawler-standalone.xml";
+ private static final String FACTORY_NAME = "crawlerStandalone";
+
+ /**
+ * Constructs the factory.
+ *
+ */
+ public StandaloneCrawlerBeanFactory() {
+ super(LOCATOR, FACTORY_NAME);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+/**
+ * Access to system properties for the crawler.
+ */
+public final class SystemProperties {
+
+ private static final String DEBUG_PROPERTY = "kiss.debug";
+
+ private static final String NO_PROGRAM_DETAILS = "kiss.nodetails";
+
+ private static final String DISABLE_RECORD = "kiss.norecord";
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private SystemProperties() {
+ // Empty.
+ }
+
+ /**
+ * Determines if the system is run in debug mode. When in debug mode, less
+ * extensive crawling is done.
+ *
+ * @return True iff we are running in debug mode.
+ */
+ public static boolean isDebugMode() {
+ return System.getProperties().getProperty(DEBUG_PROPERTY) != null;
+ }
+
+ /**
+ * Determines if no program details are required.
+ *
+ * @return True iff no program details are required.
+ */
+ public static boolean isNoProgramDetailsRequired() {
+ return System.getProperties().getProperty(NO_PROGRAM_DETAILS) != null;
+ }
+
+ /**
+ * Determines if recording is disabled.
+ *
+ * @return True iff no recording should be done.
+ */
+ public static boolean isRecordDisabled() {
+ return System.getProperties().getProperty(DISABLE_RECORD) != null;
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package contains the crawling logic of the KiSS EPG site as well
+as the configuration classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.dom4j.Element;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * A notifier that uses SMTP to notify users by mail.
+ *
+ */
+public class MailNotifier implements Notifier {
+
+ private String _from;
+
+ private String _to;
+
+ private String _subject;
+
+ private String _htmlXslt;
+
+ private String _textXslt;
+
+ private MailServer _server;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the notifier.
+ *
+ * @param aFrom
+ * Sender mail address to use.
+ * @param aTo
+ * Recipient mail address to use.
+ * @param aSubject
+ * Subject to use in the email.
+ * @param aHtmlXslt
+ * XSLT file to transform the report into HTML.
+ * @param aTextXslt
+ * XSLT file to transform the report into text.
+ * @param aServer
+ * Mail server to use.
+ * @param aTransformer Transformer to use.
+ */
+ public MailNotifier(String aFrom, String aTo, String aSubject,
+ String aHtmlXslt, String aTextXslt, MailServer aServer, XslTransformer aTransformer) {
+ _from = aFrom;
+ _to = aTo;
+ _subject = aSubject;
+ _htmlXslt = aHtmlXslt;
+ _textXslt = aTextXslt;
+ _server = aServer;
+ _transformer = aTransformer;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.Notifier#send(org.dom4j.Element)
+ */
+ public void send(Element aReport) throws NotificationException {
+ HtmlEmail mail = new HtmlEmail();
+ try {
+ mail.setFrom(_from);
+ mail
+ .setTo(Arrays
+ .asList(new InternetAddress[] { new InternetAddress(
+ _to) }));
+ mail.setSentDate(new Date());
+ mail.setSubject(_subject);
+
+ String htmlText = transformReport(aReport, _htmlXslt);
+ String plainText = transformReport(aReport, _textXslt);
+
+ mail.setHtmlMsg(htmlText);
+ mail.setTextMsg(plainText);
+
+ _server.send(mail);
+ } catch (EmailException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (TransformerException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (AddressException e) {
+ throw new NotificationException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Transforms a report into a destination format.
+ *
+ * @param aReport
+ * Report to transform
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed result.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case of problems transforming.
+ */
+ private String transformReport(Element aReport, String aXslt)
+ throws IOException, TransformerException {
+ String reportXmlText = aReport.asXML();
+ return _transformer.textTransform(reportXmlText.getBytes(), _transformer.resolve(aXslt));
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.notification.Notifier#asHtml(org.dom4j.Element)
+ */
+ public String asHtml(Element aReport) throws IOException, TransformerException {
+ return transformReport(aReport, _htmlXslt);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.notification.Notifier#asText(org.dom4j.Element)
+ */
+ public String asText(Element aReport) throws IOException, TransformerException {
+ return transformReport(aReport, _textXslt);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+
+/**
+ * Mail server.
+ */
+public class MailServer {
+
+ private String _host;
+
+ private int _port;
+
+ private String _username;
+
+ private String _password;
+
+ /**
+ * Constructs the mail server.
+ *
+ * @param aHost
+ * Host name of the SMTP server.
+ * @param aPort
+ * Port name of the SMTP server.
+ * @param aUsername
+ * Username to use for authentication or null if no
+ * authentication is required.
+ * @param aPassword
+ * Password to use for authentication or null if no authenticatio
+ * is required.
+ */
+ public MailServer(String aHost, int aPort, String aUsername,
+ String aPassword) {
+ _host = aHost;
+ _port = aPort;
+ _username = aUsername;
+ _password = aPassword;
+ }
+
+ /**
+ * Sends an e-mail.
+ *
+ * @param aMail
+ * Mail to send.
+ * @throws EmailException
+ * In case of problems sending the mail.
+ */
+ public void send(Email aMail) throws EmailException {
+ Properties props = new Properties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.host", _host);
+ props.put("mail.smtp.port", "" + _port);
+
+ Session mailSession = Session.getInstance(props,
+ new UsernamePasswordAuthenticator(_username, _password));
+ aMail.setMailSession(mailSession);
+ aMail.send();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+/**
+ * Notification exception thrown in case of problems sending a notification to a
+ * user.
+ *
+ */
+public class NotificationException extends Exception {
+
+ /**
+ * Constructs the notification.
+ *
+ * @param aMsg
+ * Message.
+ */
+ public NotificationException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the notification.
+ *
+ * @param aMsg
+ * Message.
+ * @param aCause
+ * Cause.
+ */
+ public NotificationException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.dom4j.Element;
+
+/**
+ * Object used to send notifications about the actions of the crawler.
+ *
+ */
+public interface Notifier {
+
+ /**
+ * Sends a notification.
+ *
+ * @param aReport
+ * Report to send.
+ */
+ void send(Element aReport) throws NotificationException;
+
+ /**
+ * Converts the report to html.
+ * @param aReport Report to convert.
+ * @return
+ */
+ String asHtml(Element aReport) throws IOException, TransformerException;
+
+ /**
+ * Converts the report to text.
+ * @param aReport Report to convert.
+ * @return
+ */
+ String asText(Element aReport) throws IOException, TransformerException;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+
+/**
+ * Authenticator to supply username and password to the mail server (if needed).
+ *
+ */
+public class UsernamePasswordAuthenticator extends Authenticator {
+
+ private String _username;
+
+ private String _password;
+
+ /**
+ * Constructs the authenticator.
+ *
+ * @param aUsername
+ * User name.
+ * @param aPassword
+ * Password.
+ */
+ public UsernamePasswordAuthenticator(String aUsername, String aPassword) {
+ _username = aUsername;
+ _password = aPassword;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.mail.Authenticator#getPasswordAuthentication()
+ */
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ if (_username == null) {
+ return null;
+ }
+ return new PasswordAuthentication(_username, _password);
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+Contains the classes for notifying users of the results of crawling.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides a crawler for the KiSS electronic program guide.
+It provides automatic recording of programs that satisfy criteria specified
+by the user.
+
+The following packages are defined:
+<ul>
+ <li> {@link org.wamblee.crawler.kiss.main}: Contains the crawling functionality and
+ configuration classes.
+ </li>
+ <li> {@link org.wamblee.crawler.kiss.guide}: Contains the TV guide object model and the
+ classes for searching relevant programs in the guide.
+ </li>
+ <li> {@link org.wamblee.crawler.kiss.notification}: Contains the classes for
+ notification.
+ </li>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-overview">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <xsl:when test="contains(text(), 'Right now')">
+ <xsl:text>right-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Evening')">
+ <xsl:text>evening</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Afternoon')">
+ <xsl:text>afternoon</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Noon')">
+ <xsl:text>noon</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Morning')">
+ <xsl:text>morning</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Tomorrow')">
+ <xsl:text>tomorrow</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:value-of select="$type"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:value-of select="$type"/>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates
+ select="//xhtml:table[3]//xhtml:tr[xhtml:td[not(contains(@class, 'listCell'))]]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:tr">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[3]//xhtml:a"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="xhtml:td[3]//xhtml:a/@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:apply-templates select=".//xhtml:script"/>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+ <xsl:template match="xhtml:script">
+ <xsl:variable name="script">
+ <xsl:value-of select="."/>
+ </xsl:variable>
+ <xsl:variable name="description">
+ <xsl:value-of
+ select="substring-before(substring-after($script, '<br>'), '"]')"/>
+ </xsl:variable>
+ <description>
+ <xsl:value-of select="$description"/>
+ </description>
+
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates select="//xhtml:tr[xhtml:td[not(contains(@class, 'listCell'))]]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:tr">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[2]//xhtml:a"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="xhtml:td[2]//xhtml:a/@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name=">>" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time> </time></action>
+
+ <action name="Wintertijd" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 23:55 - 00:40 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhalingen" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+00:50 - 06:15 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:45 - 06:59 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:59 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 07:00 - 07:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:10 - 07:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:30 - 07:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:40 - 08:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:00 - 08:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:10 - 08:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 08:30 - 08:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:40 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:00 - 09:10 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:10 - 09:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:30 - 09:55 - </time></action>
+
+ <action name="Schoondochter gezocht" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:55 - 10:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 10:50 - 11:15 - </time></action>
+
+ <action name="Appeltje voor de dorst" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+11:15 - 12:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:00 - 12:10 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:10 - 12:35 - </time></action>
+
+ <action name="Voor alle fans: Drukwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:35 - 12:57 - </time></action>
+
+ <action name="Trekking Lingo" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:57 - 13:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:00 - 13:10 - </time></action>
+
+ <action name="NOS-Sportjournaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:10 - 13:20 - </time></action>
+
+ <action name="Buitenhof" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:20 - 14:15 - </time></action>
+
+ <action name="Hoge bomen in de misdaad" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:15 - 14:55 - </time></action>
+
+ <action name="AVRO Dierenpark" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:55 - 15:20 - </time></action>
+
+ <action name="Kruispunt" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>15:20 - 16:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:00 - 16:05 - </time></action>
+
+ <action name="Helden van nu: Vrijwilligers in de gezondheidszorg" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:05 - 16:30 - </time></action>
+
+ <action name="Leven met verlies" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:30 - 17:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:00 - 17:10 - </time></action>
+
+ <action name="Schepper & co" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:10 - 17:35 - </time></action>
+
+ <action name="MAX & Catherine" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:35 - 18:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:30 - 18:55 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:55 - 19:25 - </time></action>
+
+ <action name="Ingang Oost" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>19:25 - 20:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:00 - 20:30 - </time></action>
+
+ <action name="Netwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:30 - 21:05 - </time></action>
+
+ <action name="Memories" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>21:05 - 22:05 - </time></action>
+
+ <action name="Keyzer & De Boer Advocaten" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:05 - 22:55 - </time></action>
+
+ <action name="NCRV Dokument: Een familie van vaders" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:55 - 23:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>23:50 - 00:20 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:20 - 00:50 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:50 - 06:45 - </time></action>
+
+
+BackHomeLogout</channel-right-now>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a[ count(following::xhtml:a) >= 3]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:value-of select="preceding-sibling::text()[1]"/>
+ </xsl:element>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="favorite-channels">
+ <xsl:apply-templates select="//xhtml:td[contains(@class, 'listCell') and position() = 2]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:td">
+ <action name="{.//xhtml:b}" type="channel-overview"
+ reference="{xhtml:a/@href}"/>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="favorite-channels">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a[not(contains(@href, 'reload')) and text() != 'Back' and text() != 'Home' and text() != 'Logout' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type"><xsl:text>channel-overview</xsl:text></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="crawlerStandalone"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.crawler.properties.xml</value>
+ <value>org.wamblee.crawler.notification.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
+
+ <!-- =====================================================
+ By default, simply copy everything
+ ===================================================== -->
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\r
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"\r
+ version="1.0">\r
+ \r
+ <xsl:output method="xml"/>\r
+ <xsl:strip-space elements="*"/>\r
+ \r
+ <!-- =====================================================\r
+ Copying template.\r
+ ===================================================== -->\r
+ <xsl:template match="@*|node()" mode="copy">\r
+ <xsl:copy>\r
+ <xsl:apply-templates select="@*|node()" mode="copy"/>\r
+ </xsl:copy>\r
+ </xsl:template>\r
+ \r
+ <xsl:template match="/">\r
+ <xsl:element name="login">\r
+ <xsl:apply-templates select="//xhtml:table[@class = 'tvstart']"/>\r
+ </xsl:element>\r
+ </xsl:template>\r
+ \r
+ <xsl:template match="xhtml:table">\r
+ <action type="channels-whats-on-now" name="channels-whats-on-now" reference="{xhtml:tr[3]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="channels-whats-on" name="channels-whats-on" reference="{xhtml:tr[5]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="channels-favorites" name="channels-favorites" reference="{xhtml:tr[7]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="shows-whats-on" name="shows-whats-on" reference="{xhtml:tr[3]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-search" name="shows-search" reference="{xhtml:tr[5]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-favorites" name="shows-favorites" reference="{xhtml:tr[7]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-add-favorite" name="shows-add-favorite" reference="{xhtml:tr[9]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="movies-whats-on" name="movies-whats-on" reference="{xhtml:tr[3]/xhtml:td[5]//@href}"/>\r
+ \r
+ <action type="sports-whats-on" name="sports-whats-on" reference="{xhtml:tr[9]/xhtml:td[5]//@href}"/>\r
+ \r
+ \r
+ </xsl:template>\r
+ \r
+ <xsl:template match="xhtml:a">\r
+ <xsl:variable name="type">\r
+ <xsl:choose>\r
+ <!-- Everything in the Favorite Channels section --> \r
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and \r
+ contains(text(),\r
+ 'Channels')]\r
+ and following::node()[contains(text(), 'Favorite') and\r
+ contains(text(), 'Shows')]">\r
+ <favorite-channels>\r
+ <xsl:choose>\r
+ <xsl:when test="contains(text(), \r
+ 's on now?')">\r
+ <xsl:text>channels-whats-on-now</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 's on?')">\r
+ <xsl:text>channels-whats-on</xsl:text> \r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Favorites')">\r
+ <xsl:text>channels-favorites</xsl:text> \r
+ </xsl:when>\r
+ </xsl:choose>\r
+ </favorite-channels>\r
+ </xsl:when>\r
+ \r
+ <!-- Everything in the Favorite Shows section --> \r
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and \r
+ contains(text(),\r
+ 'Shows')]\r
+ and following::node()[contains(text(), 'Movies')]">\r
+ <xsl:choose>\r
+ <xsl:when test="contains(text(), 's on?')">\r
+ <xsl:text>shows-whats-on</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Search a show')">\r
+ <xsl:text>shows-search</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Favorites')">\r
+ <xsl:text>shows-favorites</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Add a favorite')">\r
+ <xsl:text>shows-add-favorite</xsl:text>\r
+ </xsl:when> \r
+ </xsl:choose> \r
+ </xsl:when>\r
+ \r
+ <!-- The Movies section -->\r
+ <xsl:when test="preceding::node()[contains(text(), 'Movies')]\r
+ and following::node()[contains(text(), 'Sports')] \r
+ and contains(text(), 's on?')">\r
+ <xsl:text>movies-whats-on</xsl:text> \r
+ </xsl:when>\r
+ \r
+ <!-- Everything in the sports section --> \r
+ \r
+ <xsl:when test="preceding::node()[contains(text(), 'Sports')] \r
+ and contains(text(), 's on?')">\r
+ <xsl:text>sports-whats-on</xsl:text> \r
+ </xsl:when>\r
+ \r
+ <xsl:when test="contains(text(), 'Logout')">\r
+ <xsl:text>logout</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Manual')">\r
+ <xsl:text>manual-recording</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'View')">\r
+ <xsl:text>view-recordings</xsl:text>\r
+ </xsl:when>\r
+ <xsl:otherwise>\r
+ <xsl:text>unknown</xsl:text>\r
+ <xsl:value-of select="text()"/>\r
+ </xsl:otherwise>\r
+ </xsl:choose>\r
+ </xsl:variable>\r
+ <xsl:element name="action">\r
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>\r
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>\r
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>\r
+ </xsl:element>\r
+ </xsl:template>\r
+ \r
+</xsl:stylesheet>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="login">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <!-- Everything in the Favorite Channels section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and
+ contains(text(),
+ 'Channels')]
+ and following::node()[contains(text(), 'Favorite') and
+ contains(text(), 'Shows')]">
+
+ <xsl:choose>
+ <xsl:when test="contains(text(),
+ 's on now?')">
+ <xsl:text>channels-whats-on-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 's on?')">
+ <xsl:text>channels-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Favorites')">
+ <xsl:text>channels-favorites</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- Everything in the Favorite Shows section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and
+ contains(text(),
+ 'Shows')]
+ and following::node()[contains(text(), 'Movies')]">
+ <xsl:choose>
+ <xsl:when test="contains(text(), 's on?')">
+ <xsl:text>shows-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Search a show')">
+ <xsl:text>shows-search</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Favorites')">
+ <xsl:text>shows-favorites</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Add a favorite')">
+ <xsl:text>shows-add-favorite</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- The Movies section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Movies')]
+ and following::node()[contains(text(), 'Sports')]
+ and contains(text(), 's on?')">
+ <xsl:text>movies-whats-on</xsl:text>
+ </xsl:when>
+
+ <!-- Everything in the sports section -->
+
+ <xsl:when test="preceding::node()[contains(text(), 'Sports')]
+ and contains(text(), 's on?')">
+ <xsl:text>sports-whats-on</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="contains(text(), 'Logout')">
+ <xsl:text>logout</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Manual')">
+ <xsl:text>manual-recording</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'View')">
+ <xsl:text>view-recordings</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>unknown</xsl:text>
+ <xsl:value-of select="text()"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="login">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 's on now?')">
+ <xsl:text>channels-whats-on-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 's on?')">
+ <xsl:text>channels-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 'Favorites')">
+ <xsl:text>channels-favorites</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 's on?')">
+ <xsl:text>shows-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Search a show')">
+ <xsl:text>shows-search</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Favorites')">
+ <xsl:text>shows-favorites</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Add a favorite')">
+ <xsl:text>shows-add-favorite</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Movies'] and contains(text(), 's on?')">
+ <xsl:text>movies-whats-on</xsl:text>
+ </xsl:when>
+
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Sports'] and contains(text(), 's on?')">
+ <xsl:text>sports-whats-on</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="contains(text(), 'Logout')">
+ <xsl:text>logout</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Manual')">
+ <xsl:text>manual-recording</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'View')">
+ <xsl:text>view-recordings</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ unknown
+ <xsl:value-of select="text()"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="loginform">
+ <xsl:apply-templates select="//xhtml:form"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="//xhtml:form">
+ <xsl:element name="action">
+ <xsl:attribute name="name">login</xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@action"/></xsl:attribute>
+ <xsl:attribute name="type">login</xsl:attribute>
+ <xsl:apply-templates select="//xhtml:input[@type='hidden']"/>
+ </xsl:element>
+
+ </xsl:template>
+
+ <xsl:template match="xhtml:input">
+ <xsl:element name="param">
+ <xsl:attribute name="name">
+ <xsl:value-of select="@name"/>
+ </xsl:attribute>
+ <xsl:attribute name="value">
+ <xsl:value-of select="@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <!-- Mail server configuration -->
+ <bean id="org.wamblee.crawler.kiss.notification.MailServer"
+ class="org.wamblee.crawler.kiss.notification.MailServer">
+ <constructor-arg><value>${org.wamblee.crawler.smtp.host}</value></constructor-arg>
+ <constructor-arg><value type="int">${org.wamblee.crawler.smtp.port}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.smtp.username}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.smtp.password}</value></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.xml.ClasspathUriResolver"
+ class="org.wamblee.xml.ClasspathUriResolver">
+ </bean>
+
+ <bean id="org.wamblee.xml.XslTransformer"
+ class="org.wamblee.xml.XslTransformer">
+ <constructor-arg><ref local="org.wamblee.xml.ClasspathUriResolver"/></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.crawler.kiss.notification.Notifier"
+ class="org.wamblee.crawler.kiss.notification.MailNotifier">
+ <constructor-arg><value>${org.wamblee.crawler.notification.from}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.notification.to}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.notification.subject}</value></constructor-arg>
+ <constructor-arg><value>reportToHtml.xsl</value></constructor-arg>
+ <constructor-arg><value>reportToText.xsl</value></constructor-arg>
+ <constructor-arg><ref local="org.wamblee.crawler.kiss.notification.MailServer"/></constructor-arg>
+ <constructor-arg><ref local="org.wamblee.xml.XslTransformer"/></constructor-arg>
+ </bean>
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="propertyBean"
+ class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+ <property name="locations">
+ <list>
+ <value>org.wamblee.crawler.properties</value>
+ </list>
+ </property>
+ </bean>
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="program-info">
+ <xsl:apply-templates select="//xhtml:a"/>
+ <xsl:element name="title">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="//xhtml:tr[1]/xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:element name="keywords">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="//xhtml:tr[3]/xhtml:td[3]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:element name="description">
+ <xsl:apply-templates select="//xhtml:tr[7]/xhtml:td[1]"/>
+ </xsl:element>
+ </xsl:element>
+ </xsl:template>
+
+
+
+ <xsl:template match="xhtml:a[ text() = 'Record' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:text>record</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>recorded</xsl:text>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="program-info">
+ <xsl:apply-templates select="//xhtml:a"/>
+ <xsl:element name="title">
+ <xsl:value-of select="//xhtml:h2[1]"/>
+ </xsl:element>
+ <xsl:element name="keywords">
+ <xsl:apply-templates select="//text()[count(preceding::xhtml:br)= 1]"/>
+ </xsl:element>
+ <xsl:element name="description">
+ <xsl:apply-templates select="//text()[count(preceding::xhtml:h2) = 2 and
+ count(following::xhtml:br) >= 4 and count(preceding::xhtml:br) >= 3]"/>
+ </xsl:element>
+ </xsl:element>
+ </xsl:template>
+
+
+
+ <xsl:template match="xhtml:a[ text() = 'Record' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:text>record</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>recorded</xsl:text>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:template match="/">
+ <xsl:element name="result">
+ <xsl:choose>
+ <xsl:when test="count(//xhtml:h1[contains(text(), 'Error')]) = 1">
+ <xsl:choose>
+ <xsl:when test="count(//xhtml:body/text()[contains(., 'already in the')]) =
+ 1">
+ <xsl:text>DUPLICATE</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(//xhtml:body/text()[contains(., 'conflicts with a
+ recording')]) =
+ 1">
+ <xsl:text>CONFLICT</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ERROR</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>OK</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:body">
+ <xsl:value-of select="text()"/>
+ </xsl:template>
+
+ <xsl:template match="text()">
+ TEXT NODE
+ <xsl:value-of select="."/>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="messages"/>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+
+ <xsl:template match="messages">
+ <h2>Messages</h2>
+ <ul>
+ <xsl:for-each select="message">
+ <li><font size="-1">
+ <xsl:value-of select="."/>
+ </font>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:include href="utilities.xsl"/>
+ <xsl:template match="report">
+ <xsl:text>KiSS crawler report</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <xsl:text>Possibly interesting programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="messages"/>
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <xsl:apply-templates select="program"/>
+ </xsl:template>
+ <xsl:template match="recorded">
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for technical reasons.</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <xsl:text>*</xsl:text>
+ <xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="name"/>
+ <xsl:text>(</xsl:text>
+ <xsl:value-of select="channel"/>
+ <xsl:text>/</xsl:text><xsl:value-of select="keywords"/>
+ <xsl:text>)</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:variable name="indent">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+ <xsl:call-template name="indent">
+ <xsl:with-param name="src">
+ <xsl:call-template name="word-wrap">
+ <xsl:with-param name="src"><xsl:value-of select="description"/></xsl:with-param>
+ <xsl:with-param name="width"><xsl:value-of select="72"/></xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="indentString">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+
+ </xsl:call-template>
+
+ <!--
+ <xsl:value-of select="$indent"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src"><xsl:value-of select="description"/></xsl:with-param>
+ <xsl:with-param name="from"><xsl:value-of select="$newline"/></xsl:with-param>
+ <xsl:with-param name="to"><xsl:value-of select="$newline"/><xsl:value-of select="$indent"/></xsl:with-param>
+ </xsl:call-template>
+ -->
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <xsl:text>Category: </xsl:text><xsl:value-of select="@name"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+
+ <xsl:template match="messages">
+ <xsl:text>Messages</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:for-each select="message">
+ <xsl:text>* </xsl:text>
+ <xsl:value-of select="."/>
+ <xsl:value-of select="$newline"/>
+ </xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../../build/trailer.xml">
+ <!ENTITY crawlerdeps SYSTEM "file:../basic/deps.xml">
+ <!ENTITY kisscrawlerdeps SYSTEM "file:../kiss/deps.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value="../.."/>
+ <property name="module.name" value="wamblee-crawler-kissweb" />
+ <property name="webroot.dir" value="WebRoot"/>
+
+ &header;
+ &crawlerdeps;
+ &kisscrawlerdeps;
+
+ <target name="module.build.deps"
+ depends="kisscrawler.src.d,servletapi.d,wamblee.kisscrawler.d,quartz.d,spring.d,jstl.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="kisscrawler.test.d,wamblee.kisscrawler.test.d">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-kissweb</artifactId>
+ <packaging>war</packaging>
+ <name>wamblee.org KiSS crawler web interface </name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-kiss</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>quartz</groupId>
+ <artifactId>quartz</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jstl</groupId>
+ <artifactId>jstl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>taglibs</groupId>
+ <artifactId>standard</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <webXml>${basedir}/src/webapp/WEB-INF/web.xml</webXml>
+ <warName>wamblee-crawler-kissweb</warName>
+ <warSourceDirectory>src/webapp</warSourceDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.util.Date;
+
+import org.wamblee.crawler.kiss.main.Report;
+
+/**
+ * Encapsulates the actual execution of the crawler.
+ * This interface makes it possible to test the scheduling logic
+ * in isolation.
+ *
+ */
+public interface CrawlerExecutor {
+
+ /**
+ * Executes the crawler.
+ * @param aDate Date the crawler is being triggered.
+ * @param The report from the crawler.
+ * @throws Exception
+ */
+ void execute(Date aDate, Report aReport) throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.util.Date;
+
+import org.wamblee.crawler.kiss.main.KissCrawler;
+import org.wamblee.crawler.kiss.main.Report;
+import org.wamblee.crawler.kiss.notification.Notifier;
+
+/**
+ * Implementation which executes the KiSS crawler for retrieving web content.
+ */
+public class CrawlerExecutorImpl implements CrawlerExecutor {
+
+ private String _crawlerConfig;
+ private String _programConfig;
+ private Notifier _notifier;
+
+ /**
+ * Constructs the crawler executor.
+ * @param aCrawlerConfig Crawler configuration file.
+ * @param aProgramConfig Program configuration file.
+ * @param aNotifier Object used to send notifications.
+ */
+ public CrawlerExecutorImpl(String aCrawlerConfig, String aProgramConfig, Notifier aNotifier) {
+ _crawlerConfig = aCrawlerConfig;
+ _programConfig = aProgramConfig;
+ _notifier = aNotifier;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler.CrawlerExecutor#execute(java.util.Date)
+ */
+ public void execute(Date aDate, Report aReport) throws Exception {
+ new KissCrawler(_crawlerConfig, _programConfig, _notifier, aReport);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+/**
+ * Interface to the scheduler specific for working with the crawler.
+ */
+public interface CrawlerScheduler {
+
+ /**
+ * Initializes the scheduler.
+ * @throws Exception In case of problems.
+ */
+ void initialize() throws Exception;
+
+ /**
+ * Checks if the crawler is running.
+ * @return True iff the crawler is running.
+ * @throws Exception In case of problems.
+ */
+ boolean isCrawlerRunning() throws Exception;
+
+ /**
+ * Schedules the crawler for immediate execution.
+ * @throws Exception In case of problems.
+ */
+ void scheduleNow() throws Exception;
+
+ /**
+ * Shuts down the scheduler.
+ * @throws Exception In case of problems.
+ */
+ void shutdown() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.crawler.kiss.main.Report;
+
+/**
+ * This class encapsulates the logic for deciding whether to
+ * run the crawler. This provides the mechanism to keep the
+ * scheduler simple (e.g. scheduling every hour) and providing
+ * more complex logic for determining whether to run the
+ * crawler.
+ */
+public class CrawlerStatus implements Serializable {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerStatus.class);
+
+ private CrawlerExecutor _crawler;
+ private Date _lastExecuted;
+ private boolean _lastResult;
+ private Exception _lastException;
+ private Report _lastReport;
+ private int _hourMin;
+ private int _hourMax;
+ private boolean _mustExecute;
+
+ /**
+ * Constructs the scheduler.
+ * The crawler will run if it is triggered in the range between the minimum (included)
+ * and maximum (included) hour of the day if either
+ * <ul>
+ * <li>it is triggered for the first time on the current day.</li>
+ * <li>an earlier crawling attempt on the same day failed. </li>
+ * </ul>
+ * @param aCrawler The interface through which the crawler is executed.
+ * @param aHourMin The crawler may only run if hour >= <code>aHourMin</code>
+ * @param aHourMax The crawler may only run if hour <= <code>aHourMax</code>
+ */
+ public CrawlerStatus(CrawlerExecutor aCrawler, int aHourMin, int aHourMax) {
+ _crawler = aCrawler;
+ _lastExecuted = new Date();
+ _lastResult = true; // the crawler will automatically run the next day.
+ _lastException = null;
+ _lastReport = null;
+ _hourMin = aHourMin;
+ _hourMax = aHourMax;
+ _mustExecute = false;
+ }
+
+ /**
+ * Determines whether or not the crawler must be run the next time it is triggered.
+ * @param aMustExecute If true then the crawler will run the next time it is triggered
+ * by the scheduler.
+ */
+ public void setMustExecute(boolean aMustExecute) {
+ _mustExecute = aMustExecute;
+ }
+
+ /**
+ * Called by a scheduled job. This determines whether the crawler must be run or
+ * not. This encapsulates the rukes for retrying and scheduling the crawler.
+ * @param aDate Time at which we are executing now.
+ */
+ public void execute(Date aDate) {
+
+ if (mustExecute(aDate)) {
+ LOG.info("Executing crawler at " + aDate);
+ Report report = new Report();
+ try {
+ _crawler.execute(aDate, report);
+ _lastResult = true;
+ _lastException = null;
+ } catch (Exception e) {
+ _lastResult = false;
+ _lastException = e;
+ } finally {
+ _lastExecuted = aDate;
+ _lastReport = report;
+ }
+ }
+ }
+
+ /**
+ * Gets the time the crawler was last executed.
+ * @return Time of last execution.
+ */
+ public Date getLastExecuted() {
+ return _lastExecuted;
+ }
+
+ /**
+ * Gets the result of the last execution.
+ * @return True iff last execution was a success.
+ */
+ public boolean getLastResult() {
+ return _lastResult;
+ }
+
+ /**
+ * Gets the exception thrown by the last execution.
+ * @return null if the last execution was successful or an exception
+ * otherwise.
+ */
+ public Exception getLastException() {
+ return _lastException;
+ }
+
+ /**
+ * Gets the last report from the scheduler.
+ * @return Report.
+ */
+ public Report getLastReport() {
+ return _lastReport;
+ }
+
+ /**
+ * Determines whether or not the crawler must be run.
+ * @param aDate Current time.
+ * @return True iff the crawler must be run.
+ */
+ private boolean mustExecute(Date aDate) {
+ if (_mustExecute) {
+ _mustExecute = false;
+ return true;
+ }
+ if ( _lastExecuted == null ) {
+ return false; // crawler must be started manually at least once after deployment.
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(aDate);
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ if ( hour < _hourMin ) {
+ return false;
+ }
+ if (hour > _hourMax ) {
+ return false;
+ }
+
+ if ( !lastExecutionWasOnSameDay(aDate)) {
+ return true; // First execution of today.
+ }
+ // last execution was on the same day.
+ if ( !_lastResult ) {
+ return true; // last execution of today was unsuccessful, retry.
+ }
+ return false; // already run successfully today.
+ }
+
+ /**
+ * Determines if the last execution was on the same day.
+ * @param aDate Current time.
+ * @return True iff last execution was on the same day.
+ */
+ private boolean lastExecutionWasOnSameDay(Date aDate) {
+ if ( _lastExecuted == null ) {
+ return false;
+ }
+ int curDay = getDayOfYear(aDate);
+ int lastDay = getDayOfYear(_lastExecuted);
+ return curDay == lastDay; // check can be invalid only if scheduling interval is one year,
+ // which is ridiculous.
+ }
+
+ /**
+ * Gets the day of the year
+ * @param aDate Date to compute day for.
+ */
+ private int getDayOfYear(Date aDate) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(aDate);
+ return calendar.get(Calendar.DAY_OF_YEAR);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling.quartz;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.StatefulJob;
+import org.wamblee.crawler.kiss.scheduling.CrawlerStatus;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * Quartz job to execute the crawler.
+ */
+public class CrawlerJob implements StatefulJob {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerJob.class);
+
+ /**
+ * Constructs the job.
+ *
+ */
+ public CrawlerJob() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
+ */
+ public void execute(JobExecutionContext aContext)
+ throws JobExecutionException {
+ LOG.info("Job triggered");
+ try {
+ CrawlerStatus schedule = BeanKernel.getBeanFactory().find(
+ CrawlerStatus.class);
+ schedule.execute(aContext.getFireTime());
+ } catch (Exception e) {
+ throw new JobExecutionException("Error executing crawler", e, false);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling.quartz;
+
+import java.util.Date;
+import java.util.List;
+
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerFactory;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
+import org.quartz.TriggerUtils;
+import org.quartz.impl.StdSchedulerFactory;
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+
+/**
+ * Interface to the Quartz scheduler.
+ */
+public class QuartzCrawlerScheduler implements CrawlerScheduler {
+
+ /**
+ *
+ */
+ private static final String TRIGGER_NAME = "interval";
+
+ /**
+ *
+ */
+ private static final String JOB_NAME = "kisscrawler";
+
+ private Scheduler _scheduler;
+
+ private int _intervalInSeconds;
+
+ /**
+ * Constructs the quartz interface.
+ * @param aIntervalInSeconds Scheduling interval in seconds.
+ * @throws SchedulerException
+ */
+ public QuartzCrawlerScheduler(int aIntervalInSeconds) throws SchedulerException {
+ SchedulerFactory schedulerFactory = new StdSchedulerFactory();
+ _scheduler = schedulerFactory.getScheduler();
+ _intervalInSeconds = aIntervalInSeconds;
+ }
+
+ /**
+ * Initializes the scheduler.
+ * @throws SchedulerException
+ */
+ public void initialize() throws SchedulerException {
+ _scheduler.start();
+
+ JobDetail jobDetail = new JobDetail(JOB_NAME, null, CrawlerJob.class);
+ Trigger trigger = TriggerUtils.makeSecondlyTrigger(_intervalInSeconds);
+ //trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));
+ trigger.setStartTime(new Date());
+ trigger.setName(TRIGGER_NAME);
+
+ _scheduler.scheduleJob(jobDetail, trigger);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler#isCrawlerRunning()
+ */
+ public boolean isCrawlerRunning() throws Exception {
+ List jobs = _scheduler.getCurrentlyExecutingJobs();
+ return jobs.size() > 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler#scheduleNow()
+ */
+ public void scheduleNow() throws Exception {
+ Trigger trigger = new SimpleTrigger("immediate", null);
+ trigger.setJobName(JOB_NAME);
+ _scheduler.scheduleJob(trigger);
+ }
+
+ /**
+ * Shuts down the scheduler.
+ * @throws SchedulerException
+ */
+ public void shutdown() throws SchedulerException {
+ _scheduler.shutdown();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.servlet;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * The mechanism for kick starting the scheduling of the KiSS crawler.
+ */
+public class Application implements ServletContextListener {
+
+ /**
+ * Constructs the listener.
+ *
+ */
+ public Application() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+ */
+ public void contextInitialized(ServletContextEvent aEvent) {
+ aEvent.getServletContext().log("KiSS Crawler initializing");
+ try {
+ getScheduler().initialize();
+ } catch (Exception e) {
+ aEvent.getServletContext().log("Error scheduling job", e);
+ return;
+ }
+ aEvent.getServletContext().log("KiSS Crawler initialized");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+ */
+ public void contextDestroyed(ServletContextEvent aEvent) {
+ aEvent.getServletContext().log("KiSS Crawler shutting down");
+ try {
+ getScheduler().shutdown();
+ } catch (Exception e) {
+ aEvent.getServletContext().log("Error scheduling job", e);
+ return;
+ }
+ aEvent.getServletContext().log("KiSS Crawler shut down complete");
+ }
+
+ /**
+ * Gets the scheduler from Spring.
+ * @return Scheduler.
+ */
+ private CrawlerScheduler getScheduler() {
+ return BeanKernel.getBeanFactory().find(CrawlerScheduler.class);
+ }
+
+ public static void main(String[] aArgs) throws Exception {
+ Application application = new Application();
+ application.getScheduler().initialize();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.servlet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.wamblee.crawler.kiss.main.Report;
+import org.wamblee.crawler.kiss.notification.Notifier;
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+import org.wamblee.crawler.kiss.scheduling.CrawlerStatus;
+import org.wamblee.general.BeanKernel;
+
+/**
+ *
+ */
+public class CrawlerServlet extends HttpServlet {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doPost(HttpServletRequest aRequest,
+ HttpServletResponse aResponse) throws ServletException, IOException {
+
+ CrawlerScheduler scheduler = BeanKernel.getBeanFactory().find(
+ CrawlerScheduler.class);
+ CrawlerStatus status = BeanKernel.getBeanFactory().find(
+ CrawlerStatus.class);
+
+ try {
+ if (aRequest.getParameter("details") != null) {
+ Report report = status.getLastReport();
+ if (report != null) {
+ Notifier notifier = BeanKernel.getBeanFactory().find(Notifier.class);
+ aResponse.setContentType("text/html");
+ OutputStream os = aResponse.getOutputStream();
+ os.write(notifier.asHtml(report.asXml()).getBytes());
+ return;
+ }
+ }
+ if (aRequest.getParameter("runnow") != null) {
+ status.setMustExecute(true);
+ scheduler.scheduleNow();
+ aResponse.sendRedirect("");
+ return;
+ }
+ aRequest.setAttribute("running", scheduler.isCrawlerRunning());
+ aRequest.setAttribute("lastExecuted", status.getLastExecuted());
+ aRequest.setAttribute("lastResult", status.getLastResult());
+ aRequest.setAttribute("lastException", status.getLastException());
+ aRequest.setAttribute("lastReport", status.getLastReport());
+ String msg = "";
+ Throwable e = status.getLastException();
+ while (e != null) {
+ msg = msg + e.getClass().getName() + ": " + e.getMessage()
+ + "<br/>";
+ e = e.getCause();
+ }
+ aRequest.setAttribute("lastMessage", msg);
+ } catch (Exception e) {
+ throw new ServletException("Error getting status", e);
+ }
+ aRequest.getRequestDispatcher("WEB-INF/overview.jsp").forward(aRequest,
+ aResponse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doGet(HttpServletRequest aRequest,
+ HttpServletResponse aResponse) throws ServletException, IOException {
+ doPost(aRequest, aResponse);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.spring;
+import org.wamblee.general.SpringBeanFactory;
+
+
+/**
+ * Bean factory for the crawler application.
+ */
+public class CrawlerBeanFactory extends SpringBeanFactory {
+ private static final String SELECTOR_NAME = "beanRefContext.xml";
+ private static final String FACTORY_NAME = "crawler";
+
+ /**
+ * Constructs the bean factory.
+ *
+ */
+ public CrawlerBeanFactory() {
+ super(SELECTOR_NAME, FACTORY_NAME);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="crawler"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.crawler.properties.xml</value>
+ <value>org.wamblee.crawler.notification.xml</value>
+ <value>org.wamblee.crawler.kiss.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+##############################################################################
+# Class name of the beanfactory used by the crawler application
+##############################################################################
+
+org.wamblee.beanfactory.class=org.wamblee.crawler.kiss.spring.CrawlerBeanFactory
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <!-- The object that tells quartz how to schedule the crawler -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerScheduler"
+ class="org.wamblee.crawler.kiss.scheduling.quartz.QuartzCrawlerScheduler">
+ <constructor-arg><value type="int">3600</value></constructor-arg>
+ </bean>
+
+ <!-- The object which executes the crawler -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerExecutor"
+ class="org.wamblee.crawler.kiss.scheduling.CrawlerExecutorImpl">
+ <constructor-arg><value>${org.wamblee.crawler.config.epg}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.config.programs}</value></constructor-arg>
+ <constructor-arg><ref bean="org.wamblee.crawler.kiss.notification.Notifier"/></constructor-arg>
+ </bean>
+
+ <!-- The object that determines whether to execute the crawler when it is signalled by
+ the scheduler. -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerStatus"
+ class="org.wamblee.crawler.kiss.scheduling.CrawlerStatus">
+ <constructor-arg><ref local="org.wamblee.crawler.kiss.scheduling.CrawlerExecutor"/></constructor-arg>
+ <!-- The interval of the day in hours [hourmin, hourmax] over which crawling will be done and
+ retried if necessary -->
+ <constructor-arg><value type="int">19</value></constructor-arg>
+ <constructor-arg><value type="int">24</value></constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+
+
+############################################################################
+# Mail server configuration
+############################################################################
+org.wamblee.crawler.smtp.host=shikra
+org.wamblee.crawler.smtp.port=25
+org.wamblee.crawler.smtp.username=
+org.wamblee.crawler.smtp.password=
+
+############################################################################
+# Mail notification configuration
+############################################################################
+org.wamblee.crawler.notification.from=kiss@wamblee.org
+org.wamblee.crawler.notification.to=erik@brakkee.org
+org.wamblee.crawler.notification.subject=Recording summary for tomorrow
+
+############################################################################
+# Configuration of the crawler
+############################################################################
+org.wamblee.crawler.config.epg=/home/erik/crawler/config.xml
+org.wamblee.crawler.config.programs=/home/erik/crawler/programs.xml
+
--- /dev/null
+Manifest-Version: 1.0\r
+Class-Path: \r
+\r
--- /dev/null
+
+<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>KiSS Crawler overview page</title>
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="expires" content="0">
+ <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
+ <meta http-equiv="description" content="This is my page">
+
+ <!--
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ -->
+ </head>
+
+ <body>
+ <h1>KiSS Crawler Overview</h1>
+
+ <TABLE border="1">
+ <tr>
+ <td>
+ Currently running:
+ </td>
+ <td>
+ <c:out value="${running}"/>
+ </td>
+ </tr>
+ <c:if test="${lastReport != null}">
+ <tr>
+ <td>
+ Last executed at:
+ </td>
+ <td>
+ <c:out value="${lastExecuted}"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last result:
+ </td>
+ <td>
+ <c:out value="${lastResult}"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last message:
+ </td>
+ <td>
+ <c:out value="${lastMessage}" escapeXml="false"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last report:
+ </td>
+ <td>
+ <a href="?details=1">details</a>
+ </td>
+ </tr>
+ </c:if>
+ </TABLE>
+ <c:if test="${!running}">
+ <FORM action="runnow">
+ <INPUT type="submit" name="runnow" value="Run Crawler as soon as possible">
+ </FORM>
+ </c:if>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.4"
+ xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+ http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+ <listener>
+ <listener-class>org.wamblee.crawler.kiss.servlet.Application</listener-class>
+ </listener>
+
+ <servlet>
+ <servlet-name>CrawlerServlet</servlet-name>
+ <servlet-class>org.wamblee.crawler.kiss.servlet.CrawlerServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>CrawlerServlet</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+</web-app>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler</artifactId>
+ <packaging>pom</packaging>
+ <version>0.2-SNAPSHOT</version>
+ <name>wamblee.org KiSS crawler all</name>
+ <url>http://wamblee.org</url>
+ <modules>
+ <module>basic</module>
+ <module>kiss</module>
+ <module>kissweb</module>
+ </modules>
+ <dependencies>
+ </dependencies>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-basic</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-kiss</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <descriptors>
+ <descriptor>kiss-application.xml</descriptor>
+ </descriptors>
+
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+##############
+# Properties used by forrest.build.xml for building the website
+# These are the defaults, un-comment them only if you need to change them.
+##############
+
+# Prints out a summary of Forrest settings for this project
+#forrest.echo=true
+
+# Project name (used to name .war file)
+#project.name=my-project
+
+# Specifies name of Forrest skin to use
+# See list at http://forrest.apache.org/docs/skins.html
+project.skin=tigris
+
+# Descriptors for plugins and skins
+# comma separated list, file:// is supported
+#forrest.skins.descriptors=http://forrest.apache.org/skins/skins.xml,file:///c:/myskins/skins.xml
+#forrest.plugins.descriptors=http://forrest.apache.org/plugins/plugins.xml,http://forrest.apache.org/plugins/whiteboard-plugins.xml
+
+##############
+# behavioural properties
+#project.menu-scheme=tab_attributes
+#project.menu-scheme=directories
+
+##############
+# layout properties
+
+# Properties that can be set to override the default locations
+#
+# Parent properties must be set. This usually means uncommenting
+# project.content-dir if any other property using it is uncommented
+
+#project.status=status.xml
+project.content-dir=docs
+#project.raw-content-dir=${project.content-dir}/content
+#project.conf-dir=${project.content-dir}/conf
+#project.sitemap-dir=${project.content-dir}
+#project.xdocs-dir=${project.content-dir}/content/xdocs
+project.resources-dir=${project.content-dir}/resources
+#project.stylesheets-dir=${project.resources-dir}/stylesheets
+#project.images-dir=${project.resources-dir}/images
+#project.schema-dir=${project.resources-dir}/schema
+#project.skins-dir=${project.content-dir}/skins
+#project.skinconf=${project.content-dir}/skinconf.xml
+#project.lib-dir=${project.content-dir}/lib
+project.classes-dir=${project.resources-dir}/properties
+#project.translations-dir=${project.content-dir}/translations
+
+
+
+##############
+# validation properties
+
+# This set of properties determine if validation is performed
+# Values are inherited unless overridden.
+# e.g. if forrest.validate=false then all others are false unless set to true.
+#forrest.validate=true
+#forrest.validate.xdocs=${forrest.validate}
+#forrest.validate.skinconf=${forrest.validate}
+#forrest.validate.sitemap=${forrest.validate}
+#forrest.validate.stylesheets=${forrest.validate}
+#forrest.validate.skins=${forrest.validate}
+#forrest.validate.skins.stylesheets=${forrest.validate.skins}
+
+# *.failonerror=(true|false) - stop when an XML file is invalid
+#forrest.validate.failonerror=true
+
+# *.excludes=(pattern) - comma-separated list of path patterns to not validate
+# e.g.
+#forrest.validate.xdocs.excludes=samples/subdir/**, samples/faq.xml
+#forrest.validate.xdocs.excludes=
+
+
+##############
+# General Forrest properties
+
+# The URL to start crawling from
+#project.start-uri=linkmap.html
+
+# Set logging level for messages printed to the console
+# (DEBUG, INFO, WARN, ERROR, FATAL_ERROR)
+#project.debuglevel=ERROR
+
+# Max memory to allocate to Java
+#forrest.maxmemory=64m
+
+# Any other arguments to pass to the JVM. For example, to run on an X-less
+# server, set to -Djava.awt.headless=true
+#forrest.jvmargs=
+
+# The bugtracking URL - the issue number will be appended
+#project.bugtracking-url=http://issues.apache.org/bugzilla/show_bug.cgi?id=
+#project.bugtracking-url=http://issues.apache.org/jira/browse/
+
+# The issues list as rss
+#project.issues-rss-url=
+
+#I18n Property. Based on the locale request for the browser.
+#If you want to use it for static site then modify the JVM system.language
+# and run once per language
+#project.i18n=true
+
+# The names of plugins that are required to build the project
+# comma separated list (no spaces)
+# You can request a specific version by appending "-VERSION" to the end of
+# the plugin name. If you exclude a version number the latest released version
+# will be used, however, be aware that this may be a development version. In
+# a production environment it is recomended that you specify a known working
+# version.
+# Run "forrest available-plugins" for a list of plug-ins currently available
+project.required.plugins=org.apache.forrest.plugin.output.pdf
+
+# Proxy configuration
+# proxy.host=
+# proxy.port=
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../build/trailer.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value=".."/>
+ <property name="module.name" value="wamblee-gps" />
+
+ &header;
+
+ <target name="module.build.deps"
+ depends="wamblee.support.d,dom4j.d,jfreechart.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="wamblee.support.test.d">
+
+ </target>
+
+
+ &trailer;
+
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+
+/**
+ * Represents a coordinate system.
+ */
+public interface CoordinateSystem extends Serializable {
+
+ /**
+ * Conversion to a reference coordinate system.
+ * @param aCoordinates Coordinates.
+ * @return Coordinates in the reference system.
+ */
+ Coordinates toReferenceSystem(Coordinates aCoordinates);
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+/**
+ * Coordinates in some 3-dimensional coordinate system.
+ */
+public class Coordinates implements Serializable {
+
+ private double _x1;
+ private double _x2;
+ private double _x3;
+
+ /**
+ * Constructs the coordinates.
+ * @param aX1 First coordinate.
+ * @param aX2 Second coordinate.
+ * @param aX3 Third coordinate.
+ */
+ public Coordinates(double aX1, double aX2, double aX3) {
+ _x1 = aX1;
+ _x2 = aX2;
+ _x3 = aX3;
+ }
+
+ public double getX1() {
+ return _x1;
+ }
+
+ public double getX2() {
+ return _x2;
+ }
+
+ public double getX3() {
+ return _x3;
+ }
+
+ public double getX(int i) {
+ switch (i) {
+ case 1: return _x1;
+ case 2: return _x2;
+ case 3: return _x3;
+ }
+ throw new IllegalArgumentException("coordinate out of range " + i);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "(" + getX1() + ", " + getX2() + ", " + getX3() + ")";
+ }
+
+ public Coordinates add(Coordinates aC) {
+ return new Coordinates(_x1 + aC._x1, _x2 + aC._x2, _x3 + aC._x3);
+ }
+
+ public Coordinates subtract(Coordinates aC) {
+ return new Coordinates(_x1 - aC._x1, _x2 - aC._x2, _x3 - aC._x3);
+ }
+
+ public double innerProduct(Coordinates aC) {
+ return _x1 * aC._x1 + _x2 * aC._x2 + _x3 * aC._x3;
+ }
+
+ public Coordinates outerProduct(Coordinates aC) {
+ return new Coordinates(
+ _x2*aC._x3 - _x3*aC._x2,
+ -_x1*aC._x3 + _x3*aC._x1,
+ _x1*aC._x2 - _x2*aC._x1
+ );
+ }
+
+ public double norm() {
+ return Math.sqrt(innerProduct(this));
+ }
+
+ public Coordinates scale(double aMultiplier) {
+ return new Coordinates(_x1*aMultiplier, _x2*aMultiplier, _x3*aMultiplier);
+ }
+
+ public Coordinates normalize() {
+ return scale(1.0/norm());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import org.wamblee.general.Pair;
+
+/**
+ * Represents a plane. Usually used to represent a tangent plane to the earth surface to
+ * locally approximate the earth as flat.
+ */
+public class Plane {
+
+ private static final double EPS = 1e-4;
+
+ private Coordinates _point;
+ private Coordinates _normal;
+ private Coordinates _north;
+ private Coordinates _east;
+
+ /**
+ * Constructs a plane.
+ * @param aPoint Point on the plane.
+ * @param aNormal Normal, not necessarily normalized.
+ */
+ public Plane(Point aPoint, Point aNormal) {
+ _point = aPoint.getReferenceCoordinates();
+ _normal = aNormal.getReferenceCoordinates().normalize();
+ Coordinates north = new Coordinates(0.0, 0.0, 1.0);
+ _north = north.subtract(_normal.scale(north.innerProduct(_normal))).normalize();
+ _east = _north.outerProduct(_normal);
+
+ if ( _normal.innerProduct(_north) > EPS ) {
+ throw new IllegalArgumentException("North access is not within the plane");
+ }
+ }
+
+ /**
+ * Projects a point onto the plane.
+ * @param aPoint Point to project.
+ * @return Projected point.
+ */
+ private Coordinates project(Point aPoint) {
+ Coordinates ref = aPoint.getReferenceCoordinates();
+ double lambda = _normal.innerProduct(
+ _point.subtract(ref));
+ return ref.add(_normal.scale(lambda));
+ }
+
+ /**
+ * Returns normalized coordinates within the plane of the projection of a point.
+ */
+ public Pair<Double,Double> normalizedProjection(Point aPoint) {
+ Coordinates projection = project(aPoint);
+ Coordinates delta = projection.subtract(_point);
+ double x1 = delta.innerProduct(_north);
+ double x2 = delta.innerProduct(_east);
+ return new Pair<Double,Double>(x1, x2);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+
+/**
+ * Represents a point in some coordinate system.
+ */
+public class Point implements Serializable {
+
+ private Coordinates _coordinates;
+ private CoordinateSystem _system;
+
+ /**
+ * Constructs the point.
+ * @param aCoordinates Coordinates of the point in its coordinate system.
+ * @param aSystem Coordinate system.
+ */
+ public Point(Coordinates aCoordinates, CoordinateSystem aSystem) {
+ _coordinates = aCoordinates;
+ _system = aSystem;
+ }
+
+ /**
+ * Gets the coordinates in the point's coordinate system.
+ * @return Coordinates.
+ */
+ public Coordinates getCoordinates() {
+ return _coordinates;
+ }
+
+ public Coordinates getReferenceCoordinates() {
+ return _system.toReferenceSystem(_coordinates);
+ }
+
+ /**
+ * Gets the coordinate system.
+ * @return Coordinate system.
+ */
+ public CoordinateSystem getCoordinateSystem() {
+ return _system;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getCoordinates().toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+
+/**
+ * Reference coordinate system which is the basis for defining metrics.
+ * This is a Cartesian coordinate system.
+ */
+public class ReferenceCoordinateSystem implements CoordinateSystem {
+
+ /**
+ * Constructs the coordinate system.
+ *
+ */
+ public ReferenceCoordinateSystem() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ return aCoordinates;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.gpx.CoordinateSystem#distance(org.wamblee.gpx.Coordinates,
+ * org.wamblee.gpx.Coordinates)
+ */
+ private static double distance(Coordinates aC1, Coordinates aC2) {
+ return Math.sqrt(square(aC1.getX1() - aC2.getX1())
+ + square(aC1.getX2() - aC2.getX2())
+ + square(aC1.getX3() - aC2.getX3()));
+ }
+
+ private static double square(double x) {
+ return x * x;
+ }
+
+ /**
+ * Computes the distance between two points in arbitrary coordinate systems.
+ * @param aP1 First point.
+ * @param aP2 Second point.
+ * @return Distance.
+ */
+ public static double distance(Point aP1, Point aP2) {
+ return distance( aP1.getCoordinateSystem().toReferenceSystem(aP1.getCoordinates()),
+ aP2.getCoordinateSystem().toReferenceSystem(aP2.getCoordinates()));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import static java.lang.Math.PI;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+
+
+/**
+ * Represents the coordinate system for a GPS measurement identified by
+ * <ul>
+ * <li> x1: latitude in degrees </li>
+ * <li> x2: longitude in degrees </li>
+ * <li> x3: elevation in meters </li>
+ * </ul>
+ * This coordinate system models the earth as a sphere of a specific radius.
+ */
+public class SphericalCoordinateSystem implements CoordinateSystem {
+ /**
+ * Earth radius in meters.
+ */
+ private static final double EARTH_RADIUS = 6371000;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ double latrad = radians(aCoordinates.getX1());
+ double lonrad = radians(aCoordinates.getX2());
+ double coslat = cos(latrad);
+ double sinlat = sin(latrad);
+ double coslon = cos(lonrad);
+ double sinlon = sin(lonrad);
+
+ double trueElevation = EARTH_RADIUS + aCoordinates.getX3();
+ return new Coordinates(trueElevation*coslat*coslon,
+ trueElevation*coslat*sinlon,
+ trueElevation*sinlat);
+
+ }
+
+ private double radians(double aDegrees) {
+ return aDegrees/180.0*PI;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import static java.lang.Math.PI;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+
+
+/**
+ * Represents the WGS 84 coordinate system for a GPS measurement identified by
+ * <ul>
+ * <li> x1: latitude in degrees </li>
+ * <li> x2: longitude in degrees </li>
+ * <li> x3: elevation in meters </li>
+ * </ul>
+ * WGS84 models the earth as an ellipse.
+ */
+public class Wgs84CoordinateSystem implements CoordinateSystem {
+ /*
+ * Ellipsoide parameters, where the ellipsoide is defined by
+ *
+ * (x^2 + y^2)/a^2 + z^2/b^2 = 1
+ */
+
+ /**
+ * The radius of the ellipse at the equator
+ */
+ private static final double A = 6378137.000;
+
+ /**
+ * The distance of the North and South poles to the center of the ellipsoide.
+ */
+ private static final double B = 6356752.314;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ double latrad = radians(aCoordinates.getX1());
+ double lonrad = radians(aCoordinates.getX2());
+ double coslat = cos(latrad);
+ double sinlat = sin(latrad);
+ double coslon = cos(lonrad);
+ double sinlon = sin(lonrad);
+
+ double r = A*B/Math.sqrt(B*B*coslat*coslat + A*A*sinlat*sinlat) + aCoordinates.getX3();
+
+ return new Coordinates(r*coslat*coslon,
+ r*coslat*sinlon,
+ r*sinlat);
+
+ }
+
+ private double radians(double aDegrees) {
+ return aDegrees/180.0*PI;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.track;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.gps.geometry.Point;
+
+/**
+ * Represents a GPS track.
+ */
+public class Track implements Serializable {
+
+ private List<TrackPoint> _points;
+
+ /**
+ * Constructs an empty track.
+ *
+ */
+ public Track() {
+ _points = new ArrayList<TrackPoint>();
+ }
+
+ /**
+ * Adds a point to a track.
+ * @param aPoint Point.
+ */
+ public void addPoint(TrackPoint aPoint) {
+ _points.add(aPoint);
+ }
+
+ /**
+ * @return Number of points in the track.
+ */
+ public int size() {
+ return _points.size();
+ }
+
+ public double getMinCoordinate(int i) {
+ if ( size() == 0 ) {
+ throw new IllegalArgumentException("empty track");
+ }
+ double min = getPoint(0).getCoordinates().getX(i);
+ for (int j = 1; j < size(); j++) {
+ min = Math.min(min, getPoint(j).getCoordinates().getX(i));
+ }
+ return min;
+ }
+
+ public double getMaxCoordinate(int i) {
+ if ( size() == 0 ) {
+ throw new IllegalArgumentException("empty track");
+ }
+ double max = getPoint(0).getCoordinates().getX(i);
+ for (int j = 1; j < size(); j++) {
+ max = Math.max(max, getPoint(j).getCoordinates().getX(i));
+ }
+ return max;
+ }
+
+ /**
+ * Gets the point at the given inded.
+ * @param aIndex 0 <= aIndex < size()
+ * @return Point.
+ */
+ public Point getPoint(int aIndex) {
+ return _points.get(aIndex);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.track;
+
+import org.wamblee.gps.geometry.Coordinates;
+import org.wamblee.gps.geometry.Point;
+import org.wamblee.gps.geometry.Wgs84CoordinateSystem;
+
+
+/**
+ * A point from a GPS track.
+ *
+ * TODO should be extended with additional information (e.g. date/time if available).
+ */
+public class TrackPoint extends Point {
+
+ /**
+ * Constructs the point.
+ * @param aLatitude Latitude in degrees.
+ * @param aLongitude Longitude in degrees.
+ * @param aElevation Elevation in metres.
+ */
+ public TrackPoint(double aLatitude, double aLongitude, double aElevation) {
+ super(new Coordinates(aLatitude, aLongitude, aElevation), new Wgs84CoordinateSystem());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.wamblee.gps.track.Track;
+import org.wamblee.gps.track.TrackPoint;
+import org.wamblee.xml.DomUtils;
+import org.wamblee.xml.XMLException;
+
+/**
+ * Parser for GPX tracks.
+ */
+public class GpxParser {
+
+ private static final String SCHEMA_RESOURCE = "gpx.xsd";
+
+ public GpxParser() {
+ // Empty.
+ }
+
+ public Track parse(InputStream aIs) throws XMLException {
+ Document doc = DomUtils.convert(DomUtils.read(aIs));
+ return parse(doc);
+ }
+
+ /**
+ * @param doc
+ */
+ public Track parse(Document doc) {
+ Track track = new Track();
+ Element root = doc.getRootElement().element("trk").element("trkseg");
+ for ( Iterator i =root.elementIterator("trkpt"); i.hasNext(); ) {
+ Element trkpt = (Element)i.next();
+ track.addPoint(parseTrackPoint(trkpt));
+ }
+ return track;
+ }
+
+ /**
+ * @param trkpt
+ */
+ private TrackPoint parseTrackPoint(Element trkpt) {
+ //System.out.println(trkpt.asXML() + "|\n");
+ double latitude = new Double(trkpt.attributeValue("lat"));
+ double longitude = new Double(trkpt.attributeValue("lon"));
+ Element ele = trkpt.element("ele");
+ double elevation = 0.0;
+ if ( ele != null ) {
+ elevation = new Double(ele.getText());
+ }
+ //System.out.println(" lat = " + lat + " lon = " + lon + " ele = " + ele);
+ return new TrackPoint(latitude, longitude, elevation);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.awt.Color;
+import java.awt.Image;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartFrame;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.wamblee.general.Pair;
+import org.wamblee.gps.geometry.Plane;
+import org.wamblee.gps.geometry.Point;
+import org.wamblee.gps.geometry.ReferenceCoordinateSystem;
+import org.wamblee.gps.track.Track;
+import org.wamblee.utils.JpegUtils;
+
+/**
+ * Parses a GPX file and prints out a data file with each trackpoints distance from the start of the
+ * track and its elevation, separated 0by a space.
+ */
+public class GpxPlotter {
+
+ public static void main(String[] aArgs) throws Exception {
+ File file = new File(aArgs[0]);
+ GpxParser parser = new GpxParser();
+ Track track = parser.parse(new FileInputStream(file));
+
+ List<Pair<Double,Double>> elevationProfile = computeElevationProfile(track);
+ printTrack(elevationProfile);
+ computeTotalClimb(elevationProfile);
+ plotElevationProfile(elevationProfile);
+ List<Pair<Double,Double>> trackXy = computeTrackXY(track);
+ List<Pair<Double,Double>> trackLatLon = computeTrackLatLon(track);
+ plotTrack(trackLatLon);
+ }
+
+ private static List<Pair<Double, Double>> computeElevationProfile(Track aTrack) {
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ double distance = 0.0;
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ results.add(new Pair<Double,Double>(distance, point.getCoordinates().getX3()));
+ if ( i+1 < aTrack.size()) {
+ Point nextPoint = aTrack.getPoint(i+1);
+ distance += ReferenceCoordinateSystem.distance(point, nextPoint);
+ }
+ }
+ return results;
+ }
+
+ private static List<Pair<Double, Double>> computeTrackXY(Track aTrack) {
+ Point reference = aTrack.getPoint(0);
+ Plane plane = new Plane(reference, reference); // assume the earth is spherical.
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ Pair<Double,Double> projection = plane.normalizedProjection(point);
+ results.add(projection);
+ System.out.println(point);
+ }
+ return results;
+ }
+
+ private static List<Pair<Double, Double>> computeTrackLatLon(Track aTrack) {
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ results.add(new Pair<Double,Double>(point.getCoordinates().getX1(), point.getCoordinates().getX2()));
+ }
+ return results;
+ }
+
+
+
+ private static void printTrack(List<Pair<Double,Double>> aHeightProfile) {
+ for (Pair<Double,Double> point: aHeightProfile) {
+ System.out.println(point.getFirst() + " " + point.getSecond());
+ }
+ }
+
+ private static void computeTotalClimb(List<Pair<Double,Double>> aHeightProfile) {
+ double result = 0.0;
+
+ double lastHeight = aHeightProfile.get(0).getSecond();
+ for ( int i = 1; i < aHeightProfile.size(); i++) {
+ double height = aHeightProfile.get(i).getSecond();
+ if ( height > lastHeight) {
+ result += (height-lastHeight);
+ }
+ lastHeight = height;
+ }
+ System.out.println("Total climb: " + result);
+ }
+
+ private static void plotElevationProfile(List<Pair<Double,Double>> aHeightProfile) throws IOException {
+ XYSeriesCollection dataset = createDataset(aHeightProfile, "height");
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Height Profile",
+ "Distance(m)",
+ "Height(m)",
+ dataset,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false);
+ ChartUtilities.writeChartAsPNG(new FileOutputStream("height.png"), chart, 600, 300);
+ ChartFrame frame = new ChartFrame("test", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ private static void plotTrack(List<Pair<Double,Double>> aPoints) throws IOException, InterruptedException {
+ XYSeriesCollection dataset = createDataset(aPoints, "track");
+ JFreeChart chart = createLineChart(dataset);
+
+ Pair<Pair<Double,Double>,Pair<Double,Double>> bounds = getBounds(aPoints);
+
+ chart.getXYPlot().getDomainAxis().setLowerBound(bounds.getFirst().getFirst());
+ chart.getXYPlot().getDomainAxis().setUpperBound(bounds.getFirst().getSecond());
+
+ chart.getXYPlot().getRangeAxis().setLowerBound(bounds.getSecond().getFirst());
+ chart.getXYPlot().getRangeAxis().setUpperBound(bounds.getSecond().getSecond());
+
+ Image background = JpegUtils.loadJpegImage(new FileInputStream("/home/erik/vakantie.jpg"));
+ chart.getPlot().setBackgroundImage(background);
+
+ XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer)chart.getXYPlot().getRenderer();
+ renderer.setShapesVisible(true);
+ renderer.setShapesFilled(true);
+ renderer.setPaint(Color.BLACK);
+
+ ChartUtilities.writeChartAsPNG(new FileOutputStream("test.png"), chart, 1280, 800);
+ ChartFrame frame = new ChartFrame("test", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ /**
+ * @param dataset
+ * @return
+ */
+ private static JFreeChart createLineChart(XYSeriesCollection dataset) {
+ NumberAxis xAxis = new NumberAxis("S->N");
+ xAxis.setAutoRangeIncludesZero(false);
+
+ NumberAxis yAxis = new NumberAxis("W->E");
+ yAxis.setAutoRangeIncludesZero(false);
+
+ XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
+ XYPlot plot = new ZoomableBackgroundXYPlot(dataset, xAxis, yAxis, renderer);
+ plot.setOrientation(PlotOrientation.HORIZONTAL);
+
+ JFreeChart chart = new JFreeChart(
+ "Track", JFreeChart.DEFAULT_TITLE_FONT, plot, true
+ );
+
+ return chart;
+ /*
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Track",
+ "S->N",
+ "W->E",
+ dataset,
+ PlotOrientation.HORIZONTAL,
+ true,
+ true,
+ false);
+ return chart;
+ */
+ }
+
+ /**
+ * @param aHeightProfile
+ * @return
+ */
+ private static XYSeriesCollection createDataset(List<Pair<Double, Double>> aHeightProfile, String aName) {
+ XYSeries series = new XYSeries(aName, false);
+ for (Pair<Double,Double> point: aHeightProfile) {
+ series.add(point.getFirst(), point.getSecond());
+ }
+ XYSeriesCollection dataset = new XYSeriesCollection(series);
+ return dataset;
+ }
+
+ private static Pair<Pair<Double,Double>,Pair<Double,Double>> getBounds(List<Pair<Double,Double>> aList) {
+ Pair<Double,Double> first = aList.get(0);
+ double minx= first.getFirst();
+ double maxx = minx;
+ double miny = first.getSecond();
+ double maxy = miny;
+
+ for (int i = 0; i < aList.size(); i++) {
+ Pair<Double,Double> value = aList.get(i);
+ minx = Math.min(minx, value.getFirst());
+ maxx = Math.max(maxx, value.getFirst());
+ miny = Math.min(miny, value.getSecond());
+ maxy = Math.max(maxy, value.getSecond());
+ }
+ if ( maxx == minx ) {
+ maxx += 1.0; // to avoid problems.
+ }
+ if ( maxy == miny ) {
+ maxy += 1.0; // to avoid problems.
+ }
+ final double paddingFactor = 0.3; // allow some space around min and max
+ return new Pair<Pair<Double,Double>,Pair<Double,Double>>(
+ new Pair<Double,Double>( minx - paddingFactor*(maxx-minx),
+ maxx + paddingFactor*(maxx-minx)),
+ new Pair<Double,Double>( miny - paddingFactor*(maxy-miny),
+ maxy + paddingFactor*(maxy-miny))
+ );
+ }
+}
+
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.data.xy.XYDataset;
+
+/**
+ * Extension of XY plot that provides automatic zooming of the background
+ * image.
+ */
+public class ZoomableBackgroundXYPlot extends XYPlot {
+
+ /*
+ * Initial domain axis.
+ */
+ private double _x1 = 1.0;
+ private double _x2 = -1.0; // _x2 < _x1 initially to make signify uninitialized values.
+
+ /*
+ * Initial range axis.
+ */
+ private double _y1 = 1.0;
+ private double _y2 = -1.0; // _y2 < _y1 initially to make signify uninitialized values.
+
+ public ZoomableBackgroundXYPlot(XYDataset aDataset,
+ ValueAxis aDomainAxis, ValueAxis aRangeAxis, XYItemRenderer aRenderer) {
+ super(aDataset, aDomainAxis, aRangeAxis, aRenderer);
+ }
+
+ /* (non-Javadoc)
+ * @see org.jfree.chart.plot.Plot#drawBackgroundImage(java.awt.Graphics2D, java.awt.geom.Rectangle2D)
+ */
+ @Override
+ protected void drawBackgroundImage(Graphics2D g2, Rectangle2D area) {
+ //System.out.println("--------");
+ //System.out.println("Area: " + area);
+ //System.out.println("Graphics clip: " + g2.getClipBounds());
+ //System.out.println("Domain axis: " + getDomainAxis().getLowerBound() + " " +
+ // getDomainAxis().getUpperBound());
+
+ // Get the current domain axis bounds
+ double y1 = getDomainAxis().getLowerBound();
+ double y2 = getDomainAxis().getUpperBound();
+ double x1 = getRangeAxis().getLowerBound();
+ double x2 = getRangeAxis().getUpperBound();
+
+ if ( _x2 < _x1 ) {
+ // initial domain axis bounds
+ _y1 = y1;
+ _y2 = y2;
+ _x1 = x1;
+ _x2 = x2;
+ }
+
+ Image background = getBackgroundImage();
+ int width = background.getWidth(null);
+ int height = background.getHeight(null);
+
+ // Determine the part of the image to be drawn on the screen based on the scaling
+ // of the domain axes.
+ int imageX1 = (int)Math.round(1 + (x1 - _x1)*(width-1)/(_x2 - _x1));
+ int imageX2 = (int)Math.round(1 + (x2 - _x1)*(width-1)/(_x2 - _x1));
+ // Note: y-axis of image goes from bottom to top.
+ int imageY2 = (int)Math.round(height + (y1 - _y1)*(1-height)/(_y2 - _y1));
+ int imageY1 = (int)Math.round(height + (y2 - _y1)*(1-height)/(_y2 - _y1));
+
+ // Draw the correct part of the image on the screen.
+ g2.drawImage(background, (int)area.getMinX(), (int)area.getMinY(), (int)area.getMaxX(), (int)area.getMaxY(),
+ imageX1, imageY1, imageX2, imageY2, null);
+
+ // System.out.println("========");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<xsd:schema\r
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
+ xmlns="http://www.topografix.com/GPX/1/1"\r
+ targetNamespace="http://www.topografix.com/GPX/1/1"\r
+ elementFormDefault="qualified">\r
+\r
+<xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp\r
+\r
+ GPX uses the following conventions: all coordinates are relative to the WGS84 datum. All measurements are in metric units.\r
+ </xsd:documentation>\r
+</xsd:annotation>\r
+\r
+ <xsd:element name="gpx" type="gpxType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX is the root element in the XML file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:complexType name="gpxType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements\r
+ to the extensions section of the GPX document.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="metadata" type="metadataType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Metadata about the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="wpt" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of waypoints.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="rte" type="rteType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of routes.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="trk" type="trkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of tracks.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+\r
+ <xsd:attribute name="version" type="xsd:string" use="required" fixed="1.1">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You must include the version number in your GPX document.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="creator" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You must include the name or URL of the software that created your GPX document. This allows others to\r
+ inform the creator of a GPX instance document that fails to validate.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="metadataType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Information about the GPX file, author, and copyright restrictions goes in the metadata section. Providing rich,\r
+ meaningful information about your GPX files allows others to search for and use your GPS data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The name of the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A description of the contents of the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="author" type="personType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The person or organization who created the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="copyright" type="copyrightType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Copyright and license information governing use of the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ URLs associated with the location described in the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The creation date of the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="keywords" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Keywords associated with the file. Search engines or databases can use this information to classify the data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="bounds" type="boundsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Minimum and maximum coordinates which describe the extent of the coordinates in the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="wptType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ wpt represents a waypoint, point of interest, or named feature on a map.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <!-- Position info -->\r
+ <xsd:element name="ele" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Elevation (in meters) of the point.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs. \r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="magvar" type="degreesType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Magnetic variation (in degrees) at the point\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="geoidheight" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <!-- Description info -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS waypoint comment. Sent to GPS as comment. \r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A text description of the element. Holds additional information about the element intended for the user, not the GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to additional information about the waypoint.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="sym" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of the waypoint.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <!-- Accuracy info -->\r
+ <xsd:element name="fix" type="fixType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type of GPX fix.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="sat" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Number of satellites used to calculate the GPX fix.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="hdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Horizontal dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="vdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Vertical dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="pdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Position dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="ageofdgpsdata" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Number of seconds since last DGPS update.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="dgpsid" type="dgpsStationType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ ID of DGPS station used in differential correction.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+\r
+ <xsd:attribute name="lat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="lon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="rteType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS name of route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS comment for route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text description of route for user. Not sent to GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Links to external information about the route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS route number.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ \r
+ <xsd:element name="rtept" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of route points.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="trkType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ trk represents a track - an ordered list of points describing a path.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS name of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS comment for track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ User description of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Links to external information about track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS track number.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ \r
+ <xsd:element name="trkseg" type="trksegType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+ \r
+ <xsd:complexType name="extensionsType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:any>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="trksegType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="trkpt" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="copyrightType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Information about the copyright holder and any license governing use of this file. By linking to an appropriate license,\r
+ you may place your data into the public domain or grant additional usage rights.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="year" type="xsd:gYear" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Year of copyright.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="license" type="xsd:anyURI" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to external file containing license text.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="author" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Copyright holder (TopoSoft, Inc.)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="linkType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A link to an external resource (Web page, digital photo, video clip, etc) with additional information.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="text" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text of hyperlink.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Mime type of content (image/jpeg)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="href" type="xsd:anyURI" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ URL of hyperlink.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="emailType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ An email address. Broken into two parts (id and domain) to help prevent email harvesting.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:attribute name="id" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ id half of email address (billgates2004)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="domain" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ domain half of email address (hotmail.com)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="personType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A person or organization.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Name of person or organization.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="email" type="emailType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Email address.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to Web site or other external information about person.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="ptType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A geographic point with optional elevation and time. Available for use by other schemas.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="ele" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The elevation (in meters) of the point.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The time that the point was recorded.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="lat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="lon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="ptsegType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ An ordered sequence of points. (for polygons or polylines, e.g.)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="pt" type="ptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Ordered list of geographic points.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="boundsType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Two lat/lon pairs defining the extent of an element.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:attribute name="minlat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The minimum latitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="minlon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The minimum longitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="maxlat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The maximum latitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="maxlon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The maximum longitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+\r
+ <xsd:simpleType name="latitudeType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="-90.0"/>\r
+ <xsd:maxInclusive value="90.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="longitudeType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The longitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="-180.0"/>\r
+ <xsd:maxExclusive value="180.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="degreesType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Used for bearing, heading, course. Units are decimal degrees, true (not magnetic).\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="0.0"/>\r
+ <xsd:maxExclusive value="360.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="fixType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="none"/>\r
+ <xsd:enumeration value="2d"/>\r
+ <xsd:enumeration value="3d"/>\r
+ <xsd:enumeration value="dgps"/>\r
+ <xsd:enumeration value="pps"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="dgpsStationType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Represents a differential GPS station.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:integer">\r
+ <xsd:minInclusive value="0"/>\r
+ <xsd:maxInclusive value="1023"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+</xsd:schema>\r
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.utils;
+
+import java.awt.Container;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.sun.image.codec.jpeg.JPEGCodec;
+import com.sun.image.codec.jpeg.JPEGEncodeParam;
+import com.sun.image.codec.jpeg.JPEGImageDecoder;
+import com.sun.image.codec.jpeg.JPEGImageEncoder;
+
+/**
+ * Utility functions for processing JPEG images.
+ */
+public class JpegUtils {
+ /**
+ * Scales an image preserving the aspect ratio.
+ *
+ * @param aMaxWidth Maximum width.
+ * @param aMaxHeight Maximum height.
+ * @param aImage Image to scale.
+ * @return Scaled image.
+ */
+ public static BufferedImage scaleImage(int aMaxWidth, int aMaxHeight, Image aImage) {
+ double thumbRatio = (double) aMaxWidth / (double) aMaxHeight;
+ int imageWidth = aImage.getWidth(null);
+ int imageHeight = aImage.getHeight(null);
+ double imageRatio = (double) imageWidth / (double) imageHeight;
+ if (thumbRatio < imageRatio) {
+ aMaxHeight = (int) (aMaxWidth / imageRatio);
+ } else {
+ aMaxWidth = (int) (aMaxHeight * imageRatio);
+ }
+ BufferedImage thumbImage = new BufferedImage(aMaxWidth, aMaxHeight,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics2D = thumbImage.createGraphics();
+ graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ graphics2D.drawImage(aImage, 0, 0, aMaxWidth, aMaxHeight, null);
+ return thumbImage;
+ }
+
+ /**
+ * Loads a jpeg image from an input stream.
+ *
+ * @param aInput Input stream.
+ * @return JPEG image.
+ * @throws IOException In case of IO problems.
+ * @throws InterruptedException When execution is interrupted.
+ */
+ public static BufferedImage loadJpegImage(InputStream aInput) throws IOException, InterruptedException {
+ JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(aInput);
+ BufferedImage image = decoder.decodeAsBufferedImage();
+ MediaTracker mediaTracker = new MediaTracker(new Container());
+ mediaTracker.addImage(image, 0);
+ mediaTracker.waitForID(0);
+ return image;
+ }
+
+ /**
+ * Writes a JPEG image.
+ *
+ * @param aOutput Output stream to write to.
+ * @param aQuality Quality of the JPEG image in the range 0..100
+ * @param aThumbImage
+ * @throws IOException
+ */
+ public static void writeJpegImage(OutputStream aOutput, int aQuality, BufferedImage aThumbImage) throws IOException {
+ // save thumbnail image to OUTFILE
+
+ if ( aQuality < 0 || aQuality > 100 ) {
+ throw new IllegalArgumentException("Argument quality must be in range 0.100: " + aQuality);
+ }
+
+ JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(aOutput);
+ JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(aThumbImage);
+ aQuality = Math.max(0, Math.min(aQuality, 100));
+ param.setQuality((float) aQuality / 100.0f, false);
+ encoder.setJPEGEncodeParam(param);
+ encoder.encode(aThumbImage);
+ }
+
+}
+
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>utils</artifactId>
+ <packaging>pom</packaging>
+ <version>0.2-SNAPSHOT</version>
+ <name>wamblee.org utility libraries</name>
+ <url>http://wamblee.org</url>
+ <modules>
+ <module>support</module>
+ <module>socketproxy</module>
+ <module>crawler</module>
+ </modules>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ <version>2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>jmock</groupId>
+ <artifactId>jmock-cglib</artifactId>
+ <version>1.1.0</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>cglib</groupId>
+ <artifactId>cglib-full</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>support</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>socketproxy</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>crawler-basic</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ <type>jar</type>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>quartz</groupId>
+ <artifactId>quartz</artifactId>
+ <version>1.5.1</version>
+ </dependency>
+ <dependency>
+ <groupId>jtidy</groupId>
+ <artifactId>jtidy</artifactId>
+ <version>4aug2000r7-dev</version>
+ </dependency>
+
+ <dependency>
+ <groupId>oro</groupId>
+ <artifactId>oro</artifactId>
+ <version>2.0.6</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ <version>3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>1.7.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-hibernate</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ <!-- should be possible to remove the dependence on log4j -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>1.6</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xerces</artifactId>
+ <version>2.4.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate</artifactId>
+ <version>3.0.5</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-email</groupId>
+ <artifactId>commons-email</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ <version>1.1-beta-9</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xom</groupId>
+ <artifactId>xom</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>xerces</groupId>
+ <artifactId>xmlParserAPIs</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>jstl</groupId>
+ <artifactId>jstl</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>taglibs</groupId>
+ <artifactId>standard</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+
+
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+
+ </plugins>
+
+ </build>
+</project>
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../build/trailer.xml">
+]>
+
+<project name="socketproxy" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value=".."/>
+ <property name="module.name" value="wamblee-socketproxy" />
+
+ &header;
+
+ <target name="module.build.deps"
+ depends="">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>socketproxy</artifactId>
+ <packaging>jar</packaging>
+
+ <name>wamblee.org socket proxy</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>support</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.socketproxy;
+
+public class Barrier {
+
+ private int _countLeft;
+
+ public Barrier( int aCount ) {
+ _countLeft = aCount;
+ }
+
+ public synchronized void block( ) {
+ _countLeft--;
+ if ( _countLeft < 0 ) {
+ throw new IllegalStateException(
+ "Barrier count became negative, programming error" );
+ }
+ notifyAll( );
+ while ( _countLeft > 0 ) {
+ waitUninterruptable( );
+ }
+ }
+
+ private void waitUninterruptable( ) {
+ try {
+ wait( );
+ } catch ( InterruptedException e ) {
+ // ignore.
+ }
+ }
+
+}
--- /dev/null
+package org.wamblee.socketproxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Forwarder thread which handles forwarding of an input stream to
+ * an output stream.
+ */
+
+public class ForwarderThread extends Thread {
+
+ private String _prefix;
+
+ private Barrier _barrier;
+
+ private InputStream _is;
+
+ private OutputStream _os;
+
+ /**
+ * Constructs the forwarder thread.
+ * @param aPrefix Prefix to use in the output.
+ * @param aBarrier Barrier to block on before actually closing the stream.
+ * This is done to make sure that connections are only closed in th e
+ * proxy when the forwarders in both directions are ready to close.
+ * @param aIs Input stream to read from.
+ * @param aOs Output stream to forward to.
+ */
+ public ForwarderThread( String aPrefix, Barrier aBarrier,
+ InputStream aIs, OutputStream aOs ) {
+ _prefix = aPrefix;
+ _is = aIs;
+ _os = aOs;
+ _barrier = aBarrier;
+ }
+
+ public void run( ) {
+ boolean firstChar = true;
+ try {
+ int c = _is.read( );
+ while ( c > 0 ) {
+ try {
+ _os.write( c );
+ _os.flush( );
+ if ( firstChar ) {
+ System.out.print( _prefix );
+ firstChar = false;
+ }
+ System.out.print( (char) c );
+ if ( c == '\n' ) {
+ firstChar = true;
+ }
+ } catch ( IOException e ) {
+ }
+
+ c = _is.read( );
+ }
+ } catch ( IOException e ) {
+ }
+ closeStreams();
+ }
+
+ /**
+ * @param is
+ * @param os
+ */
+ private void closeStreams( ) {
+ _barrier.block( ); // wait until the other forwarder for the other direction
+ // is also closed.
+ try {
+ _is.close( );
+ } catch ( IOException e1 ) {
+ // Empty.
+ }
+ try {
+ _os.flush( );
+ _os.close( );
+ } catch ( IOException e1 ) {
+ // Empty
+ }
+ System.out.println(_prefix + " closed");
+ }
+
+}
--- /dev/null
+package org.wamblee.socketproxy;
+
+/*
+ * Created on Apr 5, 2005
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * @author erik
+ *
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class SocketProxy {
+
+ public static void main( final String[] args ) throws IOException {
+ for ( int i = 0; i < args.length; i++ ) {
+ // System.out.println(i + " " + args[i]);
+ String[] fields = args[i].split( ":" );
+ final int localPort = Integer.parseInt( fields[0] );
+ final String host = fields[1];
+ final int remotePort = Integer.parseInt( fields[2] );
+ runSocketProxy( localPort, host, remotePort );
+ }
+ }
+
+ /**
+ * @param localPort
+ * @param host
+ * @param remotePort
+ */
+ private static void runSocketProxy( final int localPort,
+ final String host, final int remotePort ) {
+ new Thread( new Runnable( ) {
+ public void run( ) {
+ try {
+ new SocketProxy( localPort, host, remotePort );
+ } catch ( IOException e ) {
+ System.out.println( "Problem with socket " + localPort
+ + ":" + host + ":" + remotePort );
+ e.printStackTrace( );
+ }
+ }
+ } ).start( );
+ }
+
+ public SocketProxy( int localPort, String remoteHost, int remotePort )
+ throws IOException {
+ System.out.println( "Listening on port " + localPort );
+ ServerSocket server = new ServerSocket( localPort );
+ for ( ;; ) {
+ Socket socket = server.accept( );
+ System.out.println( "Got local connection on port "
+ + localPort );
+ InputStream localIs = socket.getInputStream( );
+ OutputStream localOs = socket.getOutputStream( );
+ Socket clientSocket = new Socket( remoteHost, remotePort );
+ final String description = "Port forwarding: " + localPort
+ + " -> " + remoteHost + ":" + remotePort;
+ System.out.println( description + " established." );
+ InputStream serverIs = clientSocket.getInputStream( );
+ OutputStream serverOs = clientSocket.getOutputStream( );
+ Barrier barrier = new Barrier(2);
+ final Thread t1 = runForwarder( barrier, "> ", localIs, serverOs );
+ final Thread t2 = runForwarder( barrier, "< ", serverIs, localOs );
+ waitForConnectionClose( description, t1, t2 );
+ }
+ }
+
+ /**
+ * @param description
+ * @param t1
+ * @param t2
+ */
+ private void waitForConnectionClose( final String description,
+ final Thread t1, final Thread t2 ) {
+ new Thread( new Runnable( ) {
+ public void run( ) {
+ try {
+ t1.join( );
+ t2.join( );
+ } catch ( InterruptedException e ) {
+ e.printStackTrace( );
+ }
+ System.out.println( description + " closed" );
+ }
+ } ).start( );
+ }
+
+ private Thread runForwarder( final Barrier barrier, final String prefix,
+ final InputStream is, final OutputStream os ) {
+ Thread t = new ForwarderThread(prefix, barrier, is, os);
+ t.start( );
+ return t;
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../build/trailer.xml">
+]>
+
+<project name="support" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value=".."/>
+ <property name="module.name" value="wamblee-support" />
+
+ &header;
+
+ <target name="module.build.deps"
+ depends="logging.d,commons-collections.d,commons-beanutils.d,dom4j.d,xerces.d,ehcache.d,spring.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="hibernate.standalone.d">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>support</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org support library</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-hibernate</artifactId>
+ </dependency>
+ <!-- should be possible to remove the dependence on log4j -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xerces</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>oro</groupId>
+ <artifactId>oro</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * The <code>Cache</code> interface represents... a cache.
+ * In some circumstances it is more optimal to implement caching directly in
+ * the code instead of relying on Hibernate caching methods. This interface abstracts
+ * from the used cache implementation.
+ * Cache implementations must be thread-safe.
+ */
+public interface Cache<KeyType extends Serializable, ValueType extends Serializable> {
+
+ /**
+ * Adds a key-value pair to the cache.
+ * @param aKey Key.
+ * @param aValue Value.
+ */
+ void put(KeyType aKey, ValueType aValue);
+
+ /**
+ * Retrieves a value from the cache.
+ * @param aKey Key to retrieve.
+ * @return Key.
+ */
+ ValueType get(KeyType aKey);
+
+ /**
+ * Removes an entry from the cache.
+ * @param aKey Key to remove the entry for.
+ */
+ void remove(KeyType aKey);
+
+ /**
+ * Removes all entries from the cache.
+ *
+ */
+ void clear();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Represents a cached object. The object is either retrieved from the cache if
+ * the cache has it, or a call back is invoked to get the object (and put it in
+ * the cache).
+ */
+public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
+
+ private static final Logger LOGGER = Logger.getLogger(CachedObject.class);
+
+ /**
+ * Callback invoked to compute an object if it was not found in the cache.
+ *
+ * @param <T>
+ * Type of the object
+ */
+ public static interface Computation<Key extends Serializable, Value extends Serializable> {
+ /**
+ * Gets the object. Called when the object is not in the cache.
+ *
+ * @param aObjectKey
+ * Id of the object in the cache.
+ * @return Object, must be non-null.
+ */
+ Value getObject(Key aObjectKey);
+ }
+
+ /**
+ * Cache to use.
+ */
+ private Cache<KeyType, ValueType> _cache;
+
+ /**
+ * Key of the object in the cache.
+ */
+ private KeyType _objectKey;
+
+ /**
+ * Computation used to obtain the object if it is not found in the cache.
+ */
+ private Computation<KeyType, ValueType> _computation;
+
+ /**
+ * Constructs the cached object.
+ *
+ * @param aCache
+ * Cache to use.
+ * @param aObjectKey
+ * Key of the object in the cache.
+ * @param aComputation
+ * Computation to get the object in case the object is not in the
+ * cache.
+ */
+ public CachedObject(Cache<KeyType, ValueType> aCache, KeyType aObjectKey,
+ Computation<KeyType, ValueType> aComputation) {
+ _cache = aCache;
+ _objectKey = aObjectKey;
+ _computation = aComputation;
+ }
+
+ /**
+ * Gets the object. Since the object is cached, different calls to this
+ * method may return different objects.
+ *
+ * @return Object.
+ */
+ public ValueType get() {
+ ValueType object = (ValueType) _cache.get(_objectKey); // the used
+ // cache is
+ // thread safe.
+ if (object == null) {
+ // synchronize the computation to make sure that the object is only
+ // computed
+ // once when multiple concurrent threads detect that the entry must
+ // be
+ // recomputed.
+ synchronized (this) {
+ object = (ValueType) _cache.get(_objectKey);
+ if (object == null) {
+ // No other thread did a recomputation so we must do this
+ // now.
+ LOGGER.debug("Refreshing cache for '" + _objectKey + "'");
+ object = _computation.getObject(_objectKey);
+ _cache.put(_objectKey, object);
+ }
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Invalidates the cache for the object so that it is recomputed the next
+ * time it is requested.
+ *
+ */
+ public void invalidate() {
+ _cache.remove(_objectKey);
+ }
+
+ /**
+ * Gets the cache.
+ *
+ * @return Cache.
+ */
+ public Cache getCache() {
+ return _cache;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+import org.wamblee.io.InputResource;
+
+/**
+ * Cache implemented on top of EhCache.
+ */
+public class EhCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements org.wamblee.cache.Cache<KeyType, ValueType> {
+
+ private static final Logger LOGGER = Logger.getLogger(EhCache.class);
+
+ /**
+ * EH Cache manager.
+ */
+ private CacheManager _manager;
+
+ /**
+ * EH cache.
+ */
+ private Cache _cache;
+
+ /**
+ * Constructs a cache based on EHCache.
+ *
+ * @param aResource
+ * Resource containing the configuration file for EHCache.
+ * @param aCacheName
+ * Name of the cache to use. If a cache with this name does not
+ * exist, one is created based on default settings.
+ * @throws IOException
+ * @throws CacheException
+ */
+ public EhCache(InputResource aResource, String aCacheName)
+ throws IOException, CacheException {
+ InputStream is = aResource.getInputStream();
+ try {
+ _manager = CacheManager.create(is);
+ _cache = _manager.getCache(aCacheName);
+ if (_cache == null) {
+ LOGGER.warn("Creating cache '" + aCacheName
+ + "' because it is not configured");
+ _manager.addCache(aCacheName);
+ _cache = _manager.getCache(aCacheName);
+ }
+ assert _cache != null;
+ } finally {
+ is.close();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ _cache.put(new Element(aKey, aValue));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ try {
+ Element element = _cache.get(aKey);
+ if (element == null) {
+ return null;
+ }
+ return (ValueType) element.getValue();
+ } catch (CacheException e) {
+ throw new RuntimeException("Cache problem key = '" + aKey + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ _cache.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ try {
+ _cache.removeAll();
+ } catch (IOException e) {
+ throw new RuntimeException("Problem removing items from cache", e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * A very simple cache based on a HashMap, It never expires any entries, and has
+ * no bounds on its size.
+ */
+public class ForeverCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ /**
+ * Cached entries.
+ */
+ private HashMap<KeyType, ValueType> _map;
+
+ /**
+ * Constructs the cache.
+ *
+ */
+ public ForeverCache() {
+ _map = new HashMap<KeyType, ValueType>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public synchronized void put(KeyType aKey, ValueType aValue) {
+ _map.put(aKey, aValue);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public synchronized ValueType get(KeyType aKey) {
+ return _map.get(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public synchronized void remove(KeyType aKey) {
+ _map.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public synchronized void clear() {
+ _map.clear();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * A cache that does not cache. This implementation is useful for disabling caching.
+ * Because of this implementation, application code does not need to distinguish
+ * between the situation where it a cache is used and where it isn't.
+ */
+public class ZeroCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ public ZeroCache() {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ // Empty
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ // Empty
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides an interface for a cache together with several
+implementations.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * In memory JVM lock.
+ */
+public class JvmLock implements Lock {
+
+ /**
+ * Reentrant lock to use.
+ */
+ private ReentrantLock _lock;
+
+ /**
+ * In-memory lock.
+ */
+ public JvmLock() {
+ _lock = new ReentrantLock(true);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#acquire()
+ */
+ public void acquire() {
+ _lock.lock();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#release()
+ */
+ public void release() {
+ _lock.unlock();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+/**
+ * Represents a re-entrant lock.
+ * Implementations can provide inmemory JVM locking or full cluster safe locking
+ * mechanisms.
+ */
+public interface Lock {
+
+ /**
+ * Acquires the lock.
+ */
+ void acquire();
+
+ /**
+ * Releases the lock.
+ */
+ void release();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * Locking advice. This automatically synchronized an object using a given lock.
+ */
+public class LockAdvice implements MethodInterceptor {
+
+ /**
+ * Lock to use.
+ */
+ private Lock _lock;
+
+ /**
+ * Constructs lock advice.
+ * @param aLock Lock to use.
+ */
+ public LockAdvice(Lock aLock) {
+ _lock = aLock;
+ }
+
+ /* (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ public Object invoke(MethodInvocation aInvocation) throws Throwable {
+ _lock.acquire();
+ try {
+ return aInvocation.proceed();
+ } finally {
+ _lock.release();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.HashSet;
+
+
+/**
+ * Read-write lock for allowing multiple concurrent readers or at most one
+ * writer. This implementation does not aim for high performance but for
+ * robustness and simplicity. Users of this class should not synchronize on
+ * objects of this class.
+ */
+public class ReadWriteLock {
+ /**
+ * Sets containing the references to the threads that are currently
+ * reading. This administration is useful to check that the lock has
+ * already been acquired before it is release. This check adds robustness
+ * to the application.
+ */
+ private HashSet<Thread> _readers;
+
+ /**
+ * The thread that has acquired the lock for writing or null if no such
+ * thread exists currently.
+ */
+ private Thread _writer;
+
+ /**
+ * Constructs read-write lock.
+ */
+ public ReadWriteLock() {
+ _readers = new HashSet<Thread>();
+ _writer = null;
+ }
+
+ /**
+ * Acquires the lock for reading. This call will block until the lock can
+ * be acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireRead() {
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Read lock already acquired by current thread: "
+ + Thread.currentThread());
+ }
+
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire the read lock while already holding a write lock: "
+ + Thread.currentThread());
+ }
+
+ while (_writer != null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _readers.add(Thread.currentThread());
+ }
+
+ /**
+ * Releases the lock for reading. Note: This implementation assumes that
+ * the lock has already been acquired for reading previously.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired by
+ * this thread.
+ */
+ public synchronized void releaseRead() {
+ if (!_readers.remove(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Cannot release read lock because current thread has not acquired it.");
+ }
+
+ if (_readers.size() == 0) {
+ notifyAll();
+ }
+ }
+
+ /**
+ * Acquires the lock for writing. This call will block until the lock has
+ * been acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireWrite() {
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the write lock: "
+ + Thread.currentThread());
+ }
+
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the read lock: "
+ + Thread.currentThread());
+ }
+
+ // wait until there are no more writers and no more
+ // readers
+ while ((_writer != null) || (_readers.size() > 0)) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _writer = Thread.currentThread();
+
+ // notification not necessary since all writers and
+ // readers are now blocked by this thread.
+ }
+
+ /**
+ * Releases the lock for writing.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired.
+ */
+ public synchronized void releaseWrite() {
+ if (_writer != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Cannot release write lock because it was not acquired. ");
+ }
+
+ _writer = null;
+ notifyAll();
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides utilities for dealing with concurrency.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical and of different boolean conditions.
+ */
+public class AndCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public AndCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the and condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical and.
+ */
+ public AndCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (!condition.matches(aObject)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+
+/**
+ * Determines if an object matches a certain condition.
+ */
+public interface Condition<T> {
+
+ /**
+ * Determines if an object matches a condition.
+ * @param aObject object to match.
+ * @return True iff the object matches.
+ */
+ boolean matches(T aObject);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Condition which always returns a fixed value.
+ */
+public class FixedCondition<T> implements Condition<T> {
+
+ private boolean _value;
+
+ /**
+ * Constructs the condition.
+ * @param aValue Fixed value of the condition.
+ */
+ public FixedCondition(boolean aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical or of different boolean conditions.
+ */
+public class OrCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public OrCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the or condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical or.
+ */
+ public OrCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (condition.matches(aObject)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+/**
+ * Condition to check whether a given property value matches a certain
+ * regular expression.
+ */
+public class PropertyRegexCondition<T> implements Condition<T> {
+
+ /**
+ * Property name.
+ */
+ private String _property;
+
+ /**
+ * Regular expression.
+ */
+ private Pattern _regex;
+
+ /**
+ * Whether or not to convert the value to lowercase before matching.
+ */
+ private boolean _tolower;
+
+ /**
+ * Constructs the condition.
+ * @param aProperty Name of the property to examine.
+ * @param aRegex Regular expression to use.
+ * @param aTolower Whether or not to convert the value to lowercase before matching.
+ */
+ public PropertyRegexCondition(String aProperty, String aRegex, boolean aTolower) {
+ _property = aProperty;
+ _regex = Pattern.compile(aRegex);
+ _tolower = aTolower;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ try {
+ String value = PropertyUtils.getProperty(aObject, _property) + "";
+ if ( _tolower ) {
+ value = value.toLowerCase();
+ }
+ Matcher matcher = _regex.matcher(value);
+ return matcher.matches();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides some basic support classes for checking boolean conditions
+on objects.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Bean factory used to obtain objects in a transparent way.
+ */
+public interface BeanFactory {
+
+ /**
+ * Finds a bean based on id.
+ * @param aId Id of the bean.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ Object find(String aId);
+
+ /**
+ * Finds a bean of the given class and which can be cast to the
+ * specified class. This is typically used by specifying the interface
+ * class for retrieving an implementation of that class. This
+ * means that the bean implementing the class is configured in the bean factory
+ * with id equal to the class name of the interface.
+ * @param aClass Class of the object to find.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(Class<T> aClass);
+
+ /**
+ * Finds a bean with the given id which can be cast to the specified
+ * class.
+ * @param <T> Type of the object to get.
+ * @param aId Id of the object to lookup.
+ * @param aClass Class that the object must extends.
+ * @return Object, always non-null.
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(String aId, Class<T> aClass);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Exception thrown by the BeanFactory if an object could not be found.
+ */
+public class BeanFactoryException extends RuntimeException {
+ static final long serialVersionUID = -1215992188624874902L;
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public BeanFactoryException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aThrowable Cause of the exception.
+ */
+ public BeanFactoryException(String aMsg, Throwable aThrowable) {
+ super(aMsg, aThrowable);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * The standard means to obtain the bean factory. This works by reading a
+ * property {@value #BEAN_FACTORY_CLASS} from a property file named
+ * {@value #BEAN_KERNEL_PROP_FILE} from the class path. This property identifies
+ * the bean factory implementation to use. The configured bean factory must have
+ * a no-arg constructor.
+ */
+public final class BeanKernel {
+
+ private static final Log LOG = LogFactory.getLog(BeanKernel.class);
+
+ /**
+ * Bean factory kernel properties file.
+ */
+ private static final String BEAN_KERNEL_PROP_FILE = "org.wamblee.beanfactory.properties";
+
+ /**
+ * Name of the property to define the name of the bean factory class to use.
+ * THis class must have a public default constructor.
+ */
+ private static final String BEAN_FACTORY_CLASS = "org.wamblee.beanfactory.class";
+
+ /**
+ * Cached bean factory.
+ */
+ private static BeanFactory BEAN_FACTORY;
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private BeanKernel() {
+ // Empty
+ }
+
+ /**
+ * Overrides the default mechanism for looking up the bean factory by
+ * specifying it yourself.
+ *
+ * @param aOverride
+ * Override bean factory.
+ */
+ public static void overrideBeanFactory(BeanFactory aOverride) {
+ BEAN_FACTORY = aOverride;
+ }
+
+ /**
+ * Gets the bean factory.
+ *
+ * @return Bean factory.
+ */
+ public static BeanFactory getBeanFactory() {
+ synchronized (BeanFactory.class) {
+ if (BEAN_FACTORY == null) {
+ BEAN_FACTORY = lookupBeanFactory(BEAN_KERNEL_PROP_FILE);
+ }
+ }
+ return BEAN_FACTORY;
+ }
+
+ /**
+ * Lookup the bean factory based on the properties file.
+ *
+ * @return Bean factory.
+ */
+ static BeanFactory lookupBeanFactory(String aPropertyFilename) {
+ InputResource resource = new ClassPathResource(aPropertyFilename);
+ InputStream is;
+ try {
+ is = resource.getInputStream();
+ } catch (IOException e) {
+ throw new BeanFactoryException("Cannot open resource " + resource,
+ e);
+ }
+ try {
+ Properties props = new Properties();
+ props.load(is);
+ String className = props.getProperty(BEAN_FACTORY_CLASS);
+ Class beanFactory = Class.forName(className);
+ return (BeanFactory) beanFactory.newInstance();
+ } catch (Exception e) {
+ throw new BeanFactoryException("Cannot read from resource "
+ + resource, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // last resort cannot do much now.
+ LOG.error("Error closing resource " + resource);
+ }
+ }
+ }
+}
--- /dev/null
+
+
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Represents a pair of objects. This is inspired on the C++ Standard Template
+ * Library pair template.
+ *
+ * @param <T>
+ * Type of the first object.
+ * @param <U>
+ * Type of the second object.
+ */
+public class Pair<T, U> {
+
+ private T _t;
+
+ private U _u;
+
+ /**
+ * Constructs the pair.
+ *
+ * @param aT
+ * First object.
+ * @param aU
+ * Second object.
+ */
+ public Pair(T aT, U aU) {
+ _t = aT;
+ _u = aU;
+ }
+
+ /**
+ * Copies a pair.
+ *
+ * @param aPair
+ * Pair to copy.
+ */
+ public Pair(Pair<T, U> aPair) {
+ _t = aPair._t;
+ _u = aPair._u;
+ }
+
+ /**
+ * Gets the first object of the pair.
+ *
+ * @return First object.
+ */
+ public T getFirst() {
+ return _t;
+ }
+
+ /**
+ * Gets the second object of the pair.
+ *
+ * @return Second object.
+ */
+ public U getSecond() {
+ return _u;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.access.BeanFactoryLocator;
+import org.springframework.beans.factory.access.BeanFactoryReference;
+import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
+
+/**
+ * Bean factory which uses Spring. This bean factory cannot be configured
+ * directly in the {@link org.wamblee.general.BeanKernel} because it does not
+ * provide a default no-arg constructor. Therefore, it must be delegated to or
+ * it must tbe subclassed to provide a default constructor.
+ */
+public class SpringBeanFactory implements BeanFactory {
+
+ private BeanFactoryReference _factoryReference;
+
+ /**
+ * Constructs the bean factory.
+ *
+ * @param aSelector
+ * Selector to find the appropriate bean ref context.
+ * @param aFactoryName
+ * Spring bean factory to use.
+ */
+ public SpringBeanFactory(String aSelector, String aFactoryName) {
+ try {
+ BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator
+ .getInstance(aSelector);
+ _factoryReference = locator.useBeanFactory(aFactoryName);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(
+ "Could not load bean factory: selector = '" + aSelector
+ + "', factory = '" + aFactoryName + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String,
+ * java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _factoryReference.getFactory().getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several general purpose support classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents an input resource in the classpath.
+ */
+public class ClassPathResource implements InputResource {
+ /**
+ * Resource name.
+ */
+ private String _resource;
+
+ /**
+ * Construct the class path resource.
+ *
+ * @param aResource
+ * Resource
+ */
+ public ClassPathResource(String aResource) {
+ _resource = aResource;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream stream = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(_resource);
+ if (stream == null) {
+ throw new IOException("Class path resource '" + _resource
+ + "' not found.");
+ }
+ return stream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "ClassPathResource(" + _resource + ")";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Resource implemention for reading from a file.
+ */
+public class FileResource implements InputResource {
+ /**
+ * File to read.
+ */
+ private File _file;
+
+ /**
+ * Constructs the resource.
+ *
+ * @param aFile
+ * File to read.
+ */
+ public FileResource(File aFile) {
+ _file = aFile;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(_file));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a resource from which information can be read.
+ */
+public interface InputResource {
+ /**
+ * Gets the input stream to the resource. The obtained input stream must be
+ * closed once reading has finished.
+ *
+ * @return Input stream to the resource, never null.
+ * @throws IOException
+ * in case the resource cannot be found.
+ */
+ InputStream getInputStream() throws IOException;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input resource based on an input stream.
+ */
+public class StreamResource implements InputResource {
+ /**
+ * Input stream to read.
+ */
+ private InputStream _stream;
+
+ /**
+ * Constructs a resource.
+ *
+ * @param aStream
+ * Input stream to read.
+ */
+ public StreamResource(InputStream aStream) {
+ _stream = aStream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return _stream;
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several support utilities for IO related functionality.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Default observer notifier which calls
+ * {@link org.wamblee.observer.Observer#send(ObservableType, Event)}
+ * immediately.
+ */
+public class DefaultObserverNotifier<ObservableType, Event> implements
+ ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Constructs the notifier.
+ *
+ */
+ public DefaultObserverNotifier() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.observer.ObserverNotifier#update(org.wamblee.observer.Observer,
+ * ObservableType, Event)
+ */
+ public void update(Observer<ObservableType, Event> aObserver,
+ ObservableType aObservable, Event aEvent) {
+ aObserver.send(aObservable, aEvent);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements subscription and notification logic for an observer pattern. This
+ * class is thread safe.
+ */
+public class Observable<ObservableType, Event> {
+
+ private static final Logger LOGGER = Logger.getLogger(Observable.class);
+
+ /**
+ * Observable.
+ */
+ private ObservableType _observable;
+
+ /**
+ * Used to notify observers.
+ */
+ private ObserverNotifier<ObservableType, Event> _notifier;
+
+ /**
+ * Map of subscription to observer.
+ */
+ private Map<Long, Observer<ObservableType, Event>> _observers;
+
+ /**
+ * Counter for subscriptions. Holds the next subscription.
+ */
+ private long _counter;
+
+ /**
+ * Constructs the observable.
+ *
+ * @param aObservable
+ * Observable this instance is used for.
+ * @param aNotifier
+ * Object used for implementing notification of listeners.
+ */
+ public Observable(ObservableType aObservable,
+ ObserverNotifier<ObservableType, Event> aNotifier) {
+ _observable = aObservable;
+ _notifier = aNotifier;
+ _observers = new TreeMap<Long, Observer<ObservableType, Event>>();
+ _counter = 0;
+ }
+
+ /**
+ * Subscribe an obvers.
+ *
+ * @param aObserver
+ * Observer to subscribe.
+ * @return Event Event to send.
+ */
+ public synchronized long subscribe(Observer<ObservableType, Event> aObserver) {
+ long subscription = _counter++; // integer rage is so large it will
+ // never roll over.
+ _observers.put(subscription, aObserver);
+ return subscription;
+ }
+
+ /**
+ * Unsubscribe an observer.
+ *
+ * @param aSubscription
+ * Subscription which is used
+ * @throws IllegalArgumentException
+ * In case the subscription is not known.
+ */
+ public synchronized void unsubscribe(long aSubscription) {
+ Object obj = _observers.remove(aSubscription);
+ if (obj == null) {
+ throw new IllegalArgumentException("Subscription '" + aSubscription
+ + "'");
+ }
+ }
+
+ /**
+ * Gets the number of subscribed observers.
+ *
+ * @return Number of subscribed observers.
+ */
+ public int getObserverCount() {
+ return _observers.size();
+ }
+
+ /**
+ * Notifies all subscribed observers.
+ *
+ * @param aEvent
+ * Event to send.
+ */
+ public void send(Event aEvent) {
+ // Make sure we do the notification while not holding the lock to avoid
+ // potential deadlock
+ // situations.
+ List<Observer<ObservableType, Event>> observers = new ArrayList<Observer<ObservableType, Event>>();
+ synchronized (this) {
+ observers.addAll(_observers.values());
+ }
+ for (Observer<ObservableType, Event> observer : observers) {
+ _notifier.update(observer, _observable, aEvent);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ if (_observers.size() > 0) {
+ LOGGER
+ .error("Still observers registered at finalization of observer!");
+ for (Observer observer : _observers.values()) {
+ LOGGER.error(" observer: " + observer);
+ }
+ }
+
+ super.finalize();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * This is a type-safe version of {@link java.util.Observable}.
+ */
+public interface Observer<ObservableType, Event> {
+
+ /**
+ * Called when an event has occurred on the observable.
+ * @param aObservable Observable.
+ * @param aEvent Event.
+ */
+ void send(ObservableType aObservable, Event aEvent);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Implementation of notification of subscribers.
+ */
+public interface ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Notifies an observer.
+ *
+ * @param aObserver Observer to notify
+ * @param aObservable Observable at which the event occured.
+ * @param aEvent Event that occured.
+ */
+ void update(Observer<ObservableType, Event> aObserver, ObservableType aObservable, Event aEvent);
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for the observer pattern.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of Persistent.
+ */
+public abstract class AbstractPersistent implements Persistent {
+
+ /**
+ * Primary key.
+ */
+ private Serializable _primaryKey;
+
+ /**
+ * Version.
+ */
+ private int _version;
+
+ /**
+ * Constructs the object.
+ *
+ */
+ protected AbstractPersistent() {
+ _primaryKey = null;
+ _version = -1;
+ }
+
+ /**
+ * Copy constructor.
+ * @param aPersistent Object to copy.
+ */
+ protected AbstractPersistent(AbstractPersistent aPersistent) {
+ _primaryKey = aPersistent._primaryKey;
+ _version = aPersistent._version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPrimaryKey()
+ */
+ public Serializable getPrimaryKey() {
+ return _primaryKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPrimaryKey(java.io.Serializable)
+ */
+ public void setPrimaryKey(Serializable aKey) {
+ _primaryKey = aKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPersistedVersion()
+ */
+ public int getPersistedVersion() {
+ return _version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPersistedVersion(int)
+ */
+ public void setPersistedVersion(int aVersion) {
+ _version = aVersion;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Interface for persistent objects. This defines required functionality for all objects
+ * that are persisted.
+ *
+ * Objects that implement this interface and which implement
+ * {@link java.lang.Object#equals(java.lang.Object)}
+ * should exclude the primary key and version from determining equality.
+ */
+public interface Persistent {
+
+ /**
+ * Gets the primary key.
+ * @return Primary key.
+ * @see #setPrimaryKey(Serializable)
+ */
+ Serializable getPrimaryKey();
+
+ /**
+ * Sets the primary key.
+ * @param aKey Primary key.
+ * @see #getPrimaryKey()
+ */
+ void setPrimaryKey(Serializable aKey);
+
+ /**
+ * Gets the version.
+ * @return Version.
+ * @see #setPersistedVersion(int)
+ */
+ int getPersistedVersion();
+
+ /**
+ * Sets the version.
+ * @param aVersion Version.
+ * @see #getPersistedVersion()
+ */
+ void setPersistedVersion(int aVersion);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Hibernate mapping files to use.
+ */
+public class HibernateMappingFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of hibernate mapping files.
+ *
+ */
+ public HibernateMappingFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public HibernateMappingFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Extension of
+ * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
+ */
+public class HibernateSupport extends HibernateDaoSupport {
+
+ private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
+
+ /**
+ * This class provided an equality operation based on the object reference
+ * of the wrapped object. This is required because we cannto assume that the
+ * equals operation has any meaning for different types of persistent
+ * objects. This allows us to use the standard collection classes for
+ * detecting cyclic dependences and avoiding recursion.
+ *
+ */
+ private static final class ObjectElem {
+ private Object _object;
+
+ public ObjectElem(Object aObject) {
+ _object = aObject;
+ }
+
+ public boolean equals(Object aObj) {
+ return ((ObjectElem) aObj)._object == _object;
+ }
+
+ public int hashCode() {
+ return _object.hashCode();
+ }
+ }
+
+ /**
+ * Constructs the object.
+ *
+ */
+ public HibernateSupport() {
+ // Empty
+ }
+
+ /**
+ * Performes a hibernate <code>Session.merge()</code> and updates the
+ * object with the correct primary key and version. This is an extension to
+ * the hibernate merge operation because hibernate itself leaves the object
+ * passed to merge untouched.
+ *
+ * Use this method with extreme caution since it will recursively load all
+ * objects that the current object has relations with and for which
+ * cascade="merge" was specified in the Hibernate mapping file.
+ *
+ * @param aPersistent
+ * Object to merge.
+ */
+ public void merge(Persistent aPersistent) {
+ merge(getHibernateTemplate(), aPersistent);
+ }
+
+ /**
+ * As {@link #merge(Persistent)} but with a given template. This method can
+ * be accessed in a static way.
+ *
+ * @param aTemplate
+ * Hibernate template
+ * @param aPersistent
+ * Object to merge.
+ */
+ public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
+ Persistent merged = (Persistent) aTemplate.merge(aPersistent);
+ processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
+ }
+
+ /**
+ * Copies primary keys and version from the result of the merged to the
+ * object that was passed to the merge operation. It does this by traversing
+ * the properties of the object. It copies the primary key and version for
+ * objects that implement {@link Persistent} and applies the same rules to
+ * objects in maps and sets as well (i.e. recursively).
+ *
+ * @param aPersistent
+ * Object whose primary key and version are to be set.
+ * @param aMerged
+ * Object that was the result of the merge.
+ * @param aProcessed
+ * List of already processed Persistent objects of the persistent
+ * part.
+ */
+ public static void processPersistent(Persistent aPersistent,
+ Persistent aMerged, List<ObjectElem> aProcessed) {
+ if (aPersistent == null && aMerged == null) {
+ return;
+ }
+ if (aPersistent == null || aMerged == null) {
+ throw new RuntimeException("persistent or merged object is null '"
+ + aPersistent + "'" + " '" + aMerged + "'");
+ }
+ ObjectElem elem = new ObjectElem(aPersistent);
+ if (aProcessed.contains(elem)) {
+ return; // already processed.
+ }
+ aProcessed.add(elem);
+
+ LOG.info("Setting pk/version on " + aPersistent + " from " + aMerged);
+
+ if (aPersistent.getPrimaryKey() != null
+ && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
+ LOG.error("Mismatch between primary key values: " + aPersistent
+ + " " + aMerged);
+ } else {
+ aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
+ aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
+ }
+
+ Method[] methods = aPersistent.getClass().getMethods();
+ for (Method getter : methods) {
+ if (getter.getName().startsWith("get")) {
+ Class returnType = getter.getReturnType();
+
+ try {
+ if (Set.class.isAssignableFrom(returnType)) {
+ Set merged = (Set) getter.invoke(aMerged);
+ Set persistent = (Set) getter.invoke(aPersistent);
+ processSet(persistent, merged, aProcessed);
+ } else if (List.class.isAssignableFrom(returnType)) {
+ List merged = (List) getter.invoke(aMerged);
+ List persistent = (List) getter.invoke(aPersistent);
+ processList(persistent, merged, aProcessed);
+ } else if (Map.class.isAssignableFrom(returnType)) {
+ Map merged = (Map) getter.invoke(aMerged);
+ Map persistent = (Map) getter.invoke(aPersistent);
+ processMap(persistent, merged, aProcessed);
+ } else if (Persistent.class.isAssignableFrom(returnType)) {
+ Persistent merged = (Persistent) getter.invoke(aMerged);
+ Persistent persistent = (Persistent) getter
+ .invoke(aPersistent);
+ processPersistent(persistent, merged, aProcessed);
+ } else if (returnType.isArray()
+ && Persistent.class.isAssignableFrom(returnType
+ .getComponentType())) {
+ Persistent[] merged = (Persistent[]) getter
+ .invoke(aMerged);
+ Persistent[] persistent = (Persistent[]) getter
+ .invoke(aPersistent);
+ for (int i = 0; i < persistent.length; i++) {
+ processPersistent(persistent[i], merged[i],
+ aProcessed);
+ }
+ }
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Process the persistent objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processList(List aPersistent, List aMerged,
+ List<ObjectElem> aProcessed) {
+ Object[] merged = aMerged.toArray();
+ Object[] persistent = aPersistent.toArray();
+ if (merged.length != persistent.length) {
+ throw new RuntimeException("Array sizes differ " + merged.length
+ + " " + persistent.length);
+ }
+ for (int i = 0; i < merged.length; i++) {
+ assert merged[i].equals(persistent[i]);
+ if (merged[i] instanceof Persistent) {
+ processPersistent((Persistent) persistent[i],
+ (Persistent) merged[i], aProcessed);
+ }
+ }
+ }
+
+ /**
+ * Process the persistent objects in sets.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processSet(Set aPersistent, Set aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Array sizes differ " + aMerged.size()
+ + " " + aPersistent.size());
+ }
+ for (Object merged : aMerged) {
+ // Find the object that equals the merged[i]
+ for (Object persistent : aPersistent) {
+ if (persistent.equals(merged)) {
+ processPersistent((Persistent) persistent,
+ (Persistent) merged, aProcessed);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the Map objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processMap(Map aPersistent, Map aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Sizes differ " + aMerged.size() + " "
+ + aPersistent.size());
+ }
+ Set keys = aMerged.keySet();
+ for (Object key : keys) {
+ if (!aPersistent.containsKey(key)) {
+ throw new RuntimeException("Key '" + key + "' not found");
+ }
+ Object mergedValue = aMerged.get(key);
+ Object persistentValue = aPersistent.get(key);
+ if (mergedValue instanceof Persistent) {
+ if (persistentValue instanceof Persistent) {
+ processPersistent((Persistent) persistentValue,
+ (Persistent) mergedValue, aProcessed);
+ } else {
+ throw new RuntimeException(
+ "Value in original object is null, whereas merged object contains a value");
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for persistence with hibernate.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for persistence.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * URI resolver that resolves stylesheets through the classpath.
+ */
+public class ClasspathUriResolver implements URIResolver {
+
+ /**
+ * Constructs the resolver.
+ *
+ */
+ public ClasspathUriResolver() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.xml.transform.URIResolver#resolve(java.lang.String,
+ * java.lang.String)
+ */
+ public Source resolve(String aHref, String aBase)
+ throws TransformerException {
+ InputResource xslt = new ClassPathResource(aHref);
+ try {
+ return new StreamSource(xslt.getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(
+ "Could not get XSLT style sheet in classpath '" + aHref
+ + "'", e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.dom4j.DocumentException;
+import org.dom4j.io.DOMReader;
+import org.dom4j.io.DOMWriter;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;
+import com.sun.org.apache.xerces.internal.jaxp.validation.xs.SchemaFactoryImpl;
+
+/**
+ * Some basic XML utilities for common reoccuring tasks for DOM documents.
+ */
+public final class DomUtils {
+
+ private static final Log LOG = LogFactory.getLog(DomUtils.class);
+
+ /**
+ * Disabled default constructor.
+ *
+ */
+ private DomUtils() {
+ // Empty.
+ }
+
+ /**
+ * Parses an XML document from a string.
+ *
+ * @param aDocument
+ * document.
+ * @return
+ */
+ public static Document read(String aDocument) throws XMLException {
+ ByteArrayInputStream is = new ByteArrayInputStream(aDocument.getBytes());
+ return read(is);
+ }
+
+ /**
+ * Parses an XML document from a stream.
+ *
+ * @param aIs
+ * Input stream.
+ * @return
+ */
+ public static Document read(InputStream aIs) throws XMLException {
+ try {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ return builder.parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+ }
+
+ /**
+ * Reads and validates a document against a schema.
+ *
+ * @param aIs
+ * Input stream.
+ * @param aSchema
+ * Schema.
+ * @return Parsed and validated document.
+ */
+ public static Document readAndValidate(InputStream aIs, InputStream aSchema)
+ throws XMLException {
+
+ try {
+ final Schema schema = SchemaFactory.newInstance(
+ XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
+ new StreamSource(aSchema));
+
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(true);
+ factory.setNamespaceAware(true);
+ factory.setSchema(schema);
+
+ return factory.newDocumentBuilder().parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aSchema.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing schema", e);
+ }
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+
+ }
+
+ /**
+ * Serializes an XML document to a stream.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @param aOs
+ * Output stream.
+ */
+ public static void serialize(Document aDocument, OutputStream aOs)
+ throws IOException {
+ XMLSerializer serializer = new XMLSerializer(aOs, new OutputFormat());
+ serializer.serialize(aDocument);
+ }
+
+ /**
+ * Serializes an XML document.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @return Serialized document.
+ */
+ public static String serialize(Document aDocument) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ serialize(aDocument, os);
+ return os.toString();
+ }
+
+ /**
+ * Converts a dom4j document into a w3c DOM document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return W3C DOM document.
+ */
+ public static Document convert(org.dom4j.Document aDocument)
+ throws DocumentException {
+ return new DOMWriter().write(aDocument);
+ }
+
+ /**
+ * Converts a W3C DOM document into a dom4j document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return Dom4j document.
+ */
+ public static org.dom4j.Document convert(Document aDocument) {
+ return new DOMReader().read(aDocument);
+ }
+
+ /**
+ * Removes duplicate attributes from a DOM tree.This is useful for
+ * postprocessing the output of JTidy as a workaround for a bug in JTidy.
+ *
+ * @param aNode
+ * Node to remove duplicate attributes from (recursively).
+ * Attributes of the node itself are not dealt with. Only the
+ * child nodes are dealt with.
+ */
+ public static void removeDuplicateAttributes(Node aNode) {
+ NodeList list = aNode.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element) {
+ removeDuplicateAttributes((Element) node);
+ removeDuplicateAttributes(node);
+ }
+ }
+ }
+
+ /**
+ * Removes duplicate attributes from an element.
+ *
+ * @param aElement
+ * Element.
+ */
+ private static void removeDuplicateAttributes(Element aElement) {
+ NamedNodeMap attributes = aElement.getAttributes();
+ Map<String, Attr> uniqueAttributes = new TreeMap<String, Attr>();
+ List<Attr> attlist = new ArrayList<Attr>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ if (uniqueAttributes.containsKey(attribute.getNodeName())) {
+ LOG.info("Detected duplicate attribute (will be removed)'"
+ + attribute.getNodeName() + "'");
+ }
+ uniqueAttributes.put(attribute.getNodeName(), attribute);
+ attlist.add(attribute);
+ }
+ // Remove all attributes from the element.
+ for (Attr att : attlist) {
+ aElement.removeAttributeNode(att);
+ }
+ // Add the unique attributes back to the element.
+ for (Attr att : uniqueAttributes.values()) {
+ aElement.setAttributeNode(att);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+/**
+ * Exception thrown in case of XML parsing problems.
+ */
+public class XMLException extends Exception {
+
+ public XMLException(String aMsg) {
+ super(aMsg);
+ }
+
+ public XMLException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Document;
+import org.wamblee.io.FileResource;
+
+/**
+ * XSL transformer for simplified usage of XSL transformations.
+ */
+public class XslTransformer {
+
+ private TransformerFactory _factory;
+
+ /**
+ * Constructs the URL resolver.
+ *
+ * @param aResolver
+ * URI resolver to use.
+ */
+ public XslTransformer(URIResolver aResolver) {
+ _factory = TransformerFactory.newInstance();
+ _factory.setURIResolver(aResolver);
+ }
+
+ /**
+ * Constructs the XSLT processor.
+ *
+ */
+ public XslTransformer() {
+ _factory = TransformerFactory.newInstance();
+ }
+
+ /**
+ * Resolves an XSLT based on URI.
+ * @param aXslt XSLT to resolve,
+ * @return Source for the XSLT
+ * @throws TransformerException In case the XSLT cannot be found.
+ */
+ public Source resolve(String aXslt) throws TransformerException {
+ URIResolver resolver = _factory.getURIResolver();
+ if (resolver == null) {
+ if (new File(aXslt).canRead()) {
+ try {
+ return new StreamSource(new FileResource(new File(aXslt))
+ .getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(e.getMessage(), e);
+ }
+ } else {
+ throw new TransformerException("Cannot read '" + aXslt + "'");
+ }
+ }
+ return resolver.resolve(aXslt, "");
+ }
+
+ /**
+ * Transforms a DOM document into another DOM document using a given XSLT
+ * transformation.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(Document aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new DOMSource(aDocument);
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document to a text output. This supports XSLT
+ * transformations that result in text documents.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSL transformation.
+ * @return Transformed document.
+ */
+ public String textTransform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamResult result = new StreamResult(os);
+ transform(source, result, aXslt);
+ return new String(os.toByteArray());
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aSource
+ * Document to transform.
+ * @param aResult
+ * Result of the transformation.
+ * @param aXslt
+ * XSLT to use.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public void transform(Source aSource, Result aResult, Source aXslt)
+ throws IOException, TransformerException {
+ try {
+ Transformer transformer = _factory.newTransformer(aXslt);
+ transformer.transform(aSource, aResult);
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException(
+ "Configuration problem of XSLT transformation", e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for XML processing.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=INFO
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import net.sf.ehcache.CacheException;
+
+import org.wamblee.io.TestResource;
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Cached object test.
+ */
+public class CachedObjectTest extends TestCase {
+
+ /**
+ *
+ */
+ private static final String EHCACHE_CONFIG = "ehcache.xml";
+
+ private static final int OBJECT_KEY = 10;
+
+ private CachedObject.Computation<Integer,Integer> _computation;
+ private int _ncomputations;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _computation = new CachedObject.Computation<Integer,Integer>() {
+ public Integer getObject(Integer aObjectKey) {
+ _ncomputations++;
+ return compute(aObjectKey);
+ };
+ };
+ _ncomputations = 0;
+ }
+
+ private int compute(int aValue) {
+ return aValue + 10;
+ }
+
+ private CachedObject<Integer, Integer> createCached(Cache<Integer,Integer> aCache) {
+ return new CachedObject<Integer, Integer>(aCache, OBJECT_KEY, _computation);
+ }
+
+ /**
+ * Verifies that upon first use, the cached object uses the computation to
+ * retrieve the object.
+ *
+ */
+ public void testComputation() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+
+ public void testInvalidateCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+ }
+
+ public void testBehaviorEhCache() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "test");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // explicit invalidation.
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(3, _ncomputations);
+
+ }
+
+ public void testBehaviorEhCacheDefault() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "undefined");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ }
+
+
+ public void testBehaviorForeverCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+ }
+
+ public void testBehaviorZeroCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(ncomp, _ncomputations);
+ }
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(101, _ncomputations);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.EventTracker;
+import org.wamblee.test.TimingUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the JVMLock.
+ */
+public abstract class AbstractLockTestCase extends TestCase {
+
+ protected static final int SLEEP_TIME = 1000;
+
+ protected static final String STARTED = "started";
+
+ protected static final String ACQUIRED = "acquired";
+
+ protected static final String RELEASED = "released";
+
+ private EventTracker<String> _tracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _tracker = new EventTracker<String>();
+ }
+
+ protected EventTracker<String> getTracker() {
+ return _tracker;
+ }
+
+ /**
+ * Must be implemented to generate the events
+ * {@link #STARTED}, {@link #ACQUIRED}, and {@link #RELEASED} in
+ * that order. The lock should be acquired for
+ * the time specified by {@link #SLEEP_TIME}.
+ * @return Thread which does the work.
+ */
+ protected abstract Thread runThread();
+
+ /**
+ * Tests the operation of the lock.
+ */
+ public void testLock() throws InterruptedException {
+ Thread t1 = runThread();
+ Thread t2 = runThread();
+ TimingUtils.sleep(SLEEP_TIME / 10); // give threads a chance to start
+ // up.
+ assertEquals(2, _tracker.getEventCount(STARTED)); // both threads
+ // should have
+ // started.
+ assertEquals(1, _tracker.getEventCount(ACQUIRED)); // one thread has
+ // acquired the
+ // lock.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(ACQUIRED)); // now the other
+ // thread could also
+ // acquire the lock
+ assertEquals(1, _tracker.getEventCount(RELEASED)); // and the first
+ // thread has
+ // released it.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(RELEASED)); // both threads
+ // should be
+ // finished.
+ t1.join();
+ t2.join();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Tests for the JVMLock.
+ */
+public class JvmLockTest extends AbstractLockTestCase {
+
+ private JvmLock _lock;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new JvmLock();
+ }
+
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ getTracker().eventOccurred(STARTED);
+ _lock.acquire();
+ getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ _lock.release();
+ getTracker().eventOccurred(RELEASED);
+ };
+ });
+ t.start();
+ return t;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.springframework.aop.framework.ProxyFactoryBean;
+import org.wamblee.test.TimingUtils;
+
+/**
+ *
+ */
+public class LockAdviceTest extends AbstractLockTestCase {
+
+ private class Runner implements Runnable {
+ public void run() {
+ LockAdviceTest.this.getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ }
+ }
+
+ private Runnable _target;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Runner runner = new Runner();
+ LockAdvice advice = new LockAdvice(new JvmLock());
+
+ ProxyFactoryBean support = new ProxyFactoryBean();
+ support.setInterfaces(new Class[]{ Runnable.class });
+ support.setTarget(runner);
+ support.addAdvice(advice);
+ _target = (Runnable)support.getObject();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#runThread()
+ */
+ @Override
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ getTracker().eventOccurred(STARTED);
+ _target.run();
+ getTracker().eventOccurred(RELEASED);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ };
+ });
+ t.start();
+ return t;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+
+/**
+ * Testing the read-write lock class. Note: in case of problems, test cases
+ * could hang.
+ *
+ * @see ReadWriteLock
+ */
+public class ReadWriteLockTest extends TestCase {
+ /**
+ *
+ */
+ private static final int HALF_SECOND = 500;
+ /**
+ *
+ */
+ private static final int ONE_SECOND = 1000;
+ /**
+ *
+ */
+ private static final int TWO_SECONDS = 2000;
+ private ReadWriteLock _lock;
+ private int _nReaders;
+ private int _nWriters;
+
+ /**
+ * Constructor for ReadWriteLockTest.
+ *
+ * @param aName
+ */
+ public ReadWriteLockTest(String aName) {
+ super(aName);
+ }
+
+ private synchronized int getReaderCount() {
+ return _nReaders;
+ }
+
+ private synchronized int getWriterCount() {
+ return _nWriters;
+ }
+
+ synchronized void incrementReaderCount() {
+ _nReaders++;
+ }
+
+ synchronized void incrementWriterCount() {
+ _nWriters++;
+ }
+
+ synchronized void decrementReaderCount() {
+ _nReaders--;
+ }
+
+ synchronized void decrementWriterCount() {
+ _nWriters--;
+ }
+
+ /*
+ * @see TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new ReadWriteLock();
+ }
+
+ /*
+ * @see TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ _lock = null;
+ super.tearDown();
+ }
+
+ /**
+ * Acquire and release a read lock.
+ */
+ public void testRead() {
+ _lock.acquireRead();
+ _lock.releaseRead();
+ }
+
+ /**
+ * Acquire and release a write lock.
+ */
+ public void testWrite() {
+ _lock.acquireWrite();
+ _lock.releaseWrite();
+ }
+
+ /**
+ * Verify concurrent access by multiple readers is possible.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleReaders() throws InterruptedException {
+ Runnable runnable = new ReadLocker(_lock, this, TWO_SECONDS);
+
+ Thread t1 = new Thread(runnable);
+ t1.start();
+
+ Thread t2 = new Thread(runnable);
+ t2.start();
+ Thread.sleep(ONE_SECOND);
+ assertTrue("Not enough readers!", getReaderCount() == 2);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that only one writer at a time can acquire the write lock.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testSingleWriter() throws InterruptedException {
+ WriteLocker writer = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer);
+ Thread t2 = new Thread(writer);
+
+ t1.start();
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue("Wrong writer count: " + getWriterCount(),
+ getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that multiple writers cannot acquire the write lock concurrently.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleWriters() throws InterruptedException {
+ WriteLocker writer1 = new WriteLocker(_lock, this, HALF_SECOND + ONE_SECOND);
+ WriteLocker writer2 = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer1);
+ Thread t2 = new Thread(writer2);
+
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1); // first writer still
+
+ // has the lock.
+ Thread.sleep(ONE_SECOND);
+
+ // at t = 2, the second writer still must have
+ // a lock.
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that after the first reader acquires a lock, a subsequent writer
+ * can only acquire the lock after the reader has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite1() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, reader still holding the
+ // lock so write lock cannot be acquired.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, read lock released and
+ // write lock must be acquired.
+ assertTrue("Wrong no. of readers: " + getReaderCount(),
+ getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that when multiple readers have acquired a read lock, the writer
+ * can only acquire the lock after all readers have released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite2() throws InterruptedException {
+ ReadLocker readLocker1 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ ReadLocker readLocker2 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ Thread t1 = new Thread(readLocker1);
+ Thread t2 = new Thread(readLocker2);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t3 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(ONE_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 2);
+ t3.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 2 seconds,
+ assertTrue(getReaderCount() == 2);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND);
+
+ // 3 seconds underway, first read lock must
+ // have been released.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(HALF_SECOND);
+
+ // 4 seconds underway, write lock must have
+ // been acquired.
+ assertTrue(getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+
+ t1.join();
+ t2.join();
+ t3.join();
+ }
+
+ /**
+ * Verify that after a writer acquires a lock, a subsequent reader can
+ * only acquire the lock after the writer has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite3() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t2.start(); // acquire write lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, writer still holding the
+ // lock so read lock cannot be acquired.
+ assertTrue(getWriterCount() == 1);
+ assertTrue(getReaderCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, write lock released and
+ // read lock must be acquired.
+ assertTrue("Wrong no. of writers: " + getReaderCount(),
+ getWriterCount() == 0);
+ assertTrue(getReaderCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /*
+ * The following test cases are for testing whether or not
+ * the read write lock checks the locking correctly.
+ * Strictly speaking, these checks wouldn't be necessary
+ * because it involves the contract of the ReadWriteLock which
+ * must be obeyed by users of the ReadWriteLock. Nevertheless,
+ * this is tested anyway to be absolutely sure.
+ */
+
+ /**
+ * Acquire a read lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseReadFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireRead();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseRead(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseWriteFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireWrite();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseWrite(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a read lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadTwice() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ // ok
+ return;
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a write lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteTwice() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for reading and directly afterwards acquire it for
+ * writing. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for writing and directly afterwards acquire it for
+ * reading. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a read lock and release it as a write lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByReleaseaWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.releaseWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock and release it as a read lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByReleaseRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.releaseRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+}
+
+
+/**
+ * ReadLocker acquires a read lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the read
+ * lock, and performs a callback after the lock has been released.
+ */
+class ReadLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireRead();
+ _lockTest.incrementReaderCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("ReadLocker thread was interrupted."
+ + Thread.currentThread());
+ }
+
+ _lock.releaseRead();
+ _lockTest.decrementReaderCount();
+ }
+}
+
+
+/**
+ * WriteLocker acquires a write lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the write
+ * lock, and performs a callback after the lock has been released.
+ */
+class WriteLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireWrite();
+ _lockTest.incrementWriterCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("WriteLocker thread was interrupted: "
+ + Thread.currentThread());
+ }
+
+ _lock.releaseWrite();
+ _lockTest.decrementWriterCount();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ */
+public class AndConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ AndCondition<Integer> and = new AndCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, and.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ AndCondition<Integer> and = new AndCondition<Integer>(conditions);
+ assertEquals(aResult, and.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, false);
+ checkResult(false, true, false);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, false);
+ checkResult(new boolean[]{ false, true, false }, false);
+ checkResult(new boolean[]{ false, false, true }, false);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ */
+public class GreaterThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public GreaterThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject > _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ */
+public class LessThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public LessThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject < _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ */
+public class OrConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ OrCondition<Integer> or = new OrCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, or.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ OrCondition<Integer> or = new OrCondition<Integer>(conditions);
+ assertEquals(aResult, or.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, true);
+ checkResult(false, true, true);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, true);
+ checkResult(new boolean[]{ false, true, false }, true);
+ checkResult(new boolean[]{ false, false, true }, true);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link org.wamblee.conditions.PropertyRegexCondition}.
+ */
+public class PropertyRegexConditionTest extends TestCase {
+
+ private boolean match(String aProperty, String aRegex, boolean aToLower, TestBean aBean) {
+ PropertyRegexCondition<TestBean> condition = new PropertyRegexCondition<TestBean>(aProperty, aRegex, aToLower );
+ return condition.matches(aBean);
+ }
+
+ private void checkMatch(String aProperty, String aRegex, boolean aToLower, TestBean aBean, boolean aResult) {
+ assertEquals( aResult, match(aProperty, aRegex, aToLower, aBean));
+ }
+
+ /**
+ * Verifies correct matching behavior for several cases.
+ *
+ */
+ public void testMatchProperty() {
+ TestBean bean = new TestBean("Hallo");
+ checkMatch("value", "Hallo", false, bean, true);
+ checkMatch("value", "all", false, bean, false);
+ checkMatch("value", ".a.*o", false, bean, true);
+ checkMatch("value", "hallo", false, bean, false); // no match when not converting to lower case.
+ checkMatch("value", "hallo", true, bean, true); // match!
+ }
+
+ /**
+ * Uses property regex condition for non-existing property.
+ * Verifies that a runtime exception is thrown.
+ *
+ */
+ public void testWrongProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("bla", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Applies condition to a private property. Verifies that a runtime
+ * exception is thrown.
+ *
+ */
+ public void testPrivateProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("privateValue", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ *
+ */
+public class TestBean {
+
+ private String _value;
+
+ public TestBean(String aValue) {
+ _value = aValue;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ private String getPrivateValue() {
+ return _value;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the bean kernel. The lookup of the bean factory itself can be tested
+ * only partially. Using a global property file for all test cases would tie all
+ * test cases together therefore no global property file is used.
+ */
+public class BeanKernelTest extends TestCase {
+
+ /**
+ * Loads the bean factory based on a property file configuration. Verifies
+ * the correct bean factory is loaded.
+ *
+ */
+ public void testLoadBeanFactoryFromProperties() {
+ BeanFactory factory = BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel.properties");
+ assertTrue(factory instanceof TestBeanFactory);
+ }
+
+ /**
+ * Loads the bean factory based on a non-existing property file.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentPropertyFile() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-nonexistent.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Loads the bean factory based on a property file with a non-existing
+ * bean factory defined in it.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentBeanFactory() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-wrong.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Retrieves a bean factory throug the bean kernel. Verifies that beans can
+ * be retrieved.
+ *
+ */
+ public void testRetrieveFactory() {
+ BeanKernel.overrideBeanFactory(new TestBeanFactory()); // bypass
+ // default
+ // property
+ // lookup
+ BeanFactory factory = BeanKernel.getBeanFactory();
+ assertNotNull(factory);
+ assertEquals("hello", factory.find(String.class));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the pair class.
+ */
+public class PairTest extends TestCase {
+
+ public void testPair() {
+ Pair<Integer, String> pair = new Pair<Integer, String>(10, "hello");
+ assertEquals(new Integer(10), pair.getFirst());
+ assertEquals("hello", pair.getSecond());
+
+ Pair<Integer, String> pair2 = new Pair<Integer, String>(pair);
+ assertEquals(new Integer(10), pair2.getFirst());
+ assertEquals("hello", pair2.getSecond());
+
+
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the spring bean factory.
+ */
+public class SpringBeanFactoryTest extends TestCase {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ public void testExistingBeanRefContext() {
+ SpringBeanFactory factory = new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "test");
+
+ String value1 = factory.find(String.class);
+ assertEquals("hello", value1);
+ String value2 = (String) factory.find("java.lang.String");
+ assertEquals("hello", value2);
+ String value3 = factory.find("java.lang.String", String.class);
+ assertEquals("hello", value3);
+
+ try {
+ factory.find("unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ public void testUnknownBeanFactory() {
+ try {
+ new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Test bean factory.
+ */
+public class TestBeanFactory extends SpringBeanFactory {
+
+
+ public TestBeanFactory() {
+ super("org/wamblee/general/beanRefContext.xml", "test");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the classpath resource.
+ */
+public class ClassPathResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource from the class path. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource.txt");
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource from the class path. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource-nonexistent.txt");
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the file resource.
+ */
+public class FileResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource.txt"));
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource-nonexistent.txt"));
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.security.CodeSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * File system utilities.
+ */
+public final class FileSystemUtils {
+
+ private static final Log LOG = LogFactory.getLog(FileSystemUtils.class);
+
+ /**
+ * Test output directory relative to the sub project.
+ */
+ private static final String TEST_OUTPUT_DIR = "../target/testoutput";
+
+ /**
+ * Test input directory relative to the sub project.
+ */
+ private static final String TEST_INPUT_DIR = "../src/test/resources";
+
+ /*
+ * Disabled.
+ *
+ */
+ private FileSystemUtils() {
+ // Empty
+ }
+
+ /**
+ * Deletes a directory recursively. The test case will fail if the directory
+ * does not exist or if deletion fails.
+ *
+ * @param aDir
+ * Directory to delete.
+ */
+ public static void deleteDirRecursively(String aDir) {
+ deleteDirRecursively(new File(aDir));
+ }
+
+ /**
+ * Deletes a directory recursively. See {@link #deleteDirRecursively}.
+ *
+ * @param aDir
+ * Directory.
+ */
+ public static void deleteDirRecursively(File aDir) {
+ TestCase.assertTrue(aDir.isDirectory());
+
+ for (File file : aDir.listFiles()) {
+ if (file.isDirectory()) {
+ deleteDirRecursively(file);
+ } else {
+ delete(file);
+ }
+ }
+
+ delete(aDir);
+ }
+
+ /**
+ * Deletes a file or directory. The test case will fail if the file or
+ * directory does not exist or if deletion fails. Deletion of a non-empty
+ * directory will always fail.
+ *
+ * @param aFile
+ * File or directory to delete.
+ */
+ public static void delete(File aFile) {
+ TestCase.assertTrue(aFile.delete());
+ }
+
+ /**
+ * Gets a path relative to a sub project. This utility should be used to
+ * easily access file paths within a subproject without requiring any
+ * specific Eclipse configuration.
+ *
+ * @param aRelativePath
+ * Relative path.
+ * @param aTestClass
+ * Test class.
+ */
+ public static File getPath(String aRelativePath, Class aTestClass) {
+ CodeSource source = aTestClass.getProtectionDomain().getCodeSource();
+ if (source == null) {
+ LOG.warn("Could not obtain path for '" + aRelativePath
+ + "' for class " + aTestClass
+ + ", using relative path as is");
+ return new File(aRelativePath);
+ }
+ URL location = source.getLocation();
+ String protocol = location.getProtocol();
+ if (!protocol.equals("file")) {
+ LOG.warn("protocol is not 'file': " + location);
+ return new File(aRelativePath);
+ }
+
+ String path = location.getPath();
+ try {
+ path = URLDecoder.decode(location.getPath(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore it.. just don't decode
+ LOG.warn("Decoding path failed: '" + location.getPath() + "'", e );
+ }
+
+ return new File(new File(path).getParentFile(), aRelativePath);
+ }
+
+ /**
+ * Ensures that a directory hierarchy exists (recursively if needed). If it
+ * is not possible to create the directory, then the test case will fail.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.exists() && !aDir.isDirectory()) {
+ TestCase.fail("'" + aDir
+ + "' already exists and is not a directory");
+ }
+ if (aDir.exists()) {
+ return;
+ }
+ createDir(aDir.getParentFile());
+ TestCase.assertTrue("Could not create '" + aDir + "'", aDir.mkdir());
+ }
+
+ /**
+ * Gets the test output directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test output directory.
+ */
+ public static File getTestOutputDir(Class aTestClass) {
+ File file = getPath(TEST_OUTPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Gets the test input directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test input directory.
+ */
+ public static File getTestInputDir(Class aTestClass) {
+ File file = getPath(TEST_INPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Creates a directory hierarchy for the output directory of a test class if
+ * needed.
+ *
+ * @param aTestClass
+ * Test class
+ * @return Test directory.
+ */
+ public static File createTestOutputDir(Class aTestClass) {
+ File file = getTestOutputDir(aTestClass);
+ createDir(file);
+ return file;
+ }
+
+ /**
+ * Gets a test output file name. This returns a File object representing the
+ * output file and ensures that the directory where the file will be created
+ * already exists.
+ *
+ * @param aName
+ * Name of the file.
+ * @param aTestClass
+ * Test class.
+ * @return File.
+ */
+ public static File getTestOutputFile(String aName, Class aTestClass) {
+ File file = new File(getTestOutputDir(aTestClass), aName);
+ createDir(file.getParentFile());
+ return file;
+ }
+
+ public static String read(InputStream aIs) throws IOException {
+ try {
+ StringBuffer buffer = new StringBuffer();
+ int c;
+ while ((c = aIs.read()) != -1) {
+ buffer.append((char)c);
+ }
+ return buffer.toString();
+ } finally {
+ aIs.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the stream resource.
+ */
+public class StreamResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ File file = new File(
+ FileSystemUtils.getTestInputDir(StreamResourceTest.class),
+ "myresource.txt");
+ assertTrue(file.canRead());
+ InputStream fileIs = new FileInputStream(file);
+ InputResource resource = new StreamResource(fileIs);
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+
+
+/**
+ * Test resource for locating resources in the classpath.
+ */
+public class TestResource extends FileResource {
+
+ /**
+ * Test class name.
+ * @param aTestClass Test class.
+ * @param aName Name of the file to look for.
+ */
+ public TestResource(Class aTestClass, String aName) {
+ super(getFile(aTestClass, aName));
+ }
+
+ /**
+ * Computes the file path of the file to look for.
+ * @param aClass Test class name.
+ * @param aName Name of the file.
+ * @return File.
+ */
+ private static File getFile(Class aClass, String aName) {
+ File dir = FileSystemUtils.getTestInputDir(aClass);
+ return new File(dir, aName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jmock.Mock;
+import org.jmock.cglib.MockObjectTestCase;
+
+/**
+ * Test of the observer pattern implementation.
+ */
+public class ObservableTest extends MockObjectTestCase {
+
+ private static final int SUBSCRIBER_COUNT = 100;
+
+ private static final String UPDATE = "send";
+
+ private Observable<ObservableTest, String> _observable;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ _observable = new Observable<ObservableTest, String>(this,
+ new DefaultObserverNotifier());
+ }
+
+ /**
+ * Tests subscription and notification of one subscriber.
+ */
+ public void testOneObserver() {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ String message = "hallo";
+ mockObserver.expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+
+ _observable.send(message);
+ _observable.unsubscribe(subscription);
+ assertEquals(0, _observable.getObserverCount());
+
+ _observable.send(message);
+
+ }
+
+ /**
+ * Subscribes many susbcribers and sends notifications to subscribers.
+ * Verifies that unique subscription number are returned. Also verifies that
+ * the correct subscribers are notfied.
+ */
+ public void testManySubscribers() {
+ int nsubscribers = SUBSCRIBER_COUNT;
+ Mock[] mocks = new Mock[nsubscribers];
+
+ List<Long> subscriptions = new ArrayList<Long>();
+ for (int i = 0; i < nsubscribers; i++) {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ mocks[i] = mockObserver;
+ assertTrue(subscriptions.add(subscription));
+ }
+
+ assertEquals(nsubscribers, _observable.getObserverCount());
+
+ String message = "hallo";
+ for (int i = 0; i < nsubscribers; i++) {
+ mocks[i].expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+ }
+
+ _observable.send(message);
+
+ for (int i = nsubscribers / 2; i < nsubscribers; i++) {
+ _observable.unsubscribe(subscriptions.get(i));
+ }
+ assertEquals(nsubscribers - (nsubscribers - nsubscribers / 2),
+ _observable.getObserverCount());
+
+ message = "blabla";
+ for (int i = 0; i < nsubscribers / 2; i++) {
+ mocks[i].expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+ }
+ _observable.send(message);
+ }
+
+ /**
+ * Subscribes and then unsubscribes with a wrong id. Verifies that
+ * IllegalArgumentException is thrown.
+ *
+ */
+ public void testUnsubscribeWithWrongSubscription() {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ try {
+ _observable.unsubscribe(subscription + 1);
+ } catch (IllegalArgumentException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Useful assertions for use in test cases.
+ */
+public final class AssertionUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private AssertionUtils() {
+ // Empty
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aExpected
+ * Expected object array.
+ * @param aActual
+ * Actual object array.
+ */
+ public static void assertEquals(Object[] aExpected, Object[] aActual) {
+ assertEquals("", aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected array.
+ * @param aActual
+ * Actual array.
+ */
+ public static void assertEquals(String aMsg, Object[] aExpected,
+ Object[] aActual) {
+ TestCase.assertEquals(aMsg + ": Array lengths ", aExpected.length,
+ aActual.length);
+
+ for (int i = 0; i < aExpected.length; i++) {
+ TestCase.assertEquals(aMsg + ": Element " + i, aExpected[i],
+ aActual[i]);
+ }
+ }
+
+ /**
+ * Asserts that two objects are equal, and in case the object is an Object[]
+ * delegates to {@link #assertEquals(String, Object[], Object[]).
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static void assertEquals(String aMsg, Object aExpected,
+ Object aActual) {
+ if (aExpected instanceof Object[]) {
+ AssertionUtils.assertEquals(aMsg, (Object[]) aExpected,
+ (Object[]) aActual);
+
+ return;
+ }
+
+ TestCase.assertEquals(aMsg, aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two maps are equal by comparing all keys and by checking
+ * that the values for the same keys are the same.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpectedMap
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static void assertEquals(String aMsg, Map aExpectedMap, Map aActual) {
+ TestCase.assertEquals("Map sizes differ", aExpectedMap.size(), aActual
+ .size());
+
+ Set keys = aExpectedMap.keySet();
+
+ for (Iterator i = keys.iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ TestCase.assertTrue("Map does not containg entry for key:" + key,
+ aActual.containsKey(key));
+ AssertionUtils.assertEquals("Value of key " + key + " of map",
+ aExpectedMap.get(key), aActual.get(key));
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tracks the occurence of certain events in a test environment. Threads in a
+ * test environment tell the event tracker of the occurrence of certain events
+ * using {@link #eventOccurred(Event)}. Test code inspects the events sent by a
+ * thread using {@link #isEventSent(Thread, Event)}.
+ *
+ * A record is kept of every event which is sent. Therefore, the occurrence of a
+ * new event does not erase a previously sent event.
+ *
+ * @param <Event>
+ * Type of event sent from test code. Usually String will be
+ * sufficient. The event type must provide a sensible implementation
+ * of {@link java.lang.Object#equals(java.lang.Object)}.
+ */
+public class EventTracker<Event> {
+
+ private static final Log LOG = LogFactory.getLog(EventTracker.class);
+
+ /**
+ * Map of Thread object to a list of events.
+ */
+ private Map<Thread, List<Event>> _events;
+
+ /**
+ * Constructs the event tracker.
+ *
+ */
+ public EventTracker() {
+ _events = new HashMap<Thread, List<Event>>();
+ }
+
+ /**
+ * Called by a thread to inform that an event has occurred.
+ *
+ * @param aEvent
+ * Event that was sent.
+ */
+ public synchronized void eventOccurred(Event aEvent) {
+ LOG.info("Event '" + aEvent + "' sent.");
+ Thread current = Thread.currentThread();
+ List<Event> events = _events.get(current);
+ if (events == null) {
+ events = new ArrayList<Event>();
+ _events.put(current, events);
+ }
+ events.add(aEvent);
+ }
+
+ /**
+ * Checks if a specific event has happened in a specific thread.
+ *
+ * @param aThread
+ * Thread to check.
+ * @param aEvent
+ * Event to check for.
+ * @return Whether or not the event was sent.
+ */
+ public synchronized boolean isEventSent(Thread aThread, Event aEvent) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ return false;
+ }
+ return events.contains(aEvent);
+ }
+
+ /**
+ * Gets the events for a thread in the order they were sent
+ *
+ * @param aThread
+ * Thread to get events for.
+ * @return Events that were sent. A zero-sized array is returned if no
+ * events were sent.
+ */
+ public synchronized List<Event> getEvents(Thread aThread) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ events = Collections.emptyList();
+ }
+ return Collections.unmodifiableList(events);
+ }
+
+ /**
+ * Gets the number of times an event was sent summed up
+ * over all threads.
+ *
+ * @param aEvent
+ * Event to check.
+ * @return Number of times it was reached.
+ */
+ public synchronized int getEventCount(Event aEvent) {
+ int count = 0;
+ for (Thread thread : _events.keySet()) {
+ List<Event> events = _events.get(thread);
+ for (Event event : events) {
+ if (event.equals(aEvent)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+
+/**
+ * Exporting the hibernate mapping.
+ */
+public final class HibernateExporter {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateExporter() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(aArgs[1]);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaExport export = new SchemaExport(conf);
+ export.setDelimiter(";");
+ export.setOutputFile(file);
+ export.create(true, false);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaUpdate;
+
+/**
+ * Exporting the hibernate mapping.
+ */
+public final class HibernateUpdater {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateUpdater() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(file);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaUpdate lSchemaUpdate = new SchemaUpdate(conf);
+ lSchemaUpdate.execute(true, true);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.oro.io.AwkFilenameFilter;
+import org.hibernate.cfg.Configuration;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * Hibernate utilities.
+ */
+public final class HibernateUtils {
+
+ private static final String DATABASE_PROPS = "test.database.properties";
+
+ /**
+ * Disabled.
+ *
+ */
+ private HibernateUtils() {
+ // Empty
+ }
+
+ /**
+ * @param aDir
+ * @return
+ */
+ public static Configuration getConfiguration(File aDir) throws IOException {
+ Configuration conf = new Configuration();
+ File[] files = aDir.listFiles((FileFilter) (new AwkFilenameFilter(
+ ".*\\.hbm\\.xml")));
+ for (File f : files) {
+ System.out.println("Mapping file: " + f);
+ conf.addFile(f);
+ }
+
+ Map<String, String> dbProps = getHibernateProperties();
+
+ for (Map.Entry<String, String> entry : dbProps.entrySet()) {
+ System.out.println("Property: " + entry.getKey() + "="
+ + entry.getValue());
+ conf.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ return conf;
+ }
+
+ private static Map<String, String> getHibernateProperties()
+ throws IOException {
+
+ System.out.println("Reading properties file: " + DATABASE_PROPS);
+ InputResource lPropFile = new ClassPathResource(DATABASE_PROPS);
+ Properties props = new Properties();
+ props.load(lPropFile.getInputStream());
+
+ Map<String, String> result = new TreeMap<String, String>();
+ result.put("hibernate.connection.driver_class", props
+ .getProperty("database.driver"));
+ result.put("hibernate.connection.url", props
+ .getProperty("database.url"));
+ result.put("hibernate.connection.username", props
+ .getProperty("database.username"));
+ result.put("hibernate.connection.password", props
+ .getProperty("database.password"));
+
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Spring configuration files to use.
+ */
+public class SpringConfigFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of Spring config files.
+ *
+ */
+ public SpringConfigFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public SpringConfigFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.operation.DatabaseOperation;
+import org.hibernate.SessionFactory;
+import org.jmock.cglib.MockObjectTestCase;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ * Test case support class for spring tests.
+ */
+public class SpringTestCase extends MockObjectTestCase {
+
+ private static final Log LOG = LogFactory.getLog(SpringTestCase.class);
+
+ /**
+ * Session factory bean name.
+ */
+ private static final String SESSION_FACTORY = "sessionFactory";
+
+ /**
+ * Data source bean name.
+ */
+ private static final String DATA_SOURCE = "dataSource";
+
+ /**
+ * Transaction manager bean name.
+ */
+ private static final String TRANSACTION_MANAGER = "transactionManager";
+
+ /**
+ * Name of the ConfigFileList bean that describes the Hibernate mapping
+ * files to use.
+ */
+ private static final String HIBERNATE_CONFIG_FILES = "hibernateMappingFiles";
+
+ /**
+ * Schema pattern.
+ */
+ private static final String SCHEMA_PATTERN = "%";
+
+ /**
+ * List of (String) configuration file locations for spring.
+ */
+ private String[] _configLocations;
+
+ /**
+ * Application context for storing bean definitions that vary on a test by
+ * test basis and cannot be hardcoded in the spring configuration files.
+ */
+ private GenericApplicationContext _parentContext;
+
+ /**
+ * Cached spring application context.
+ */
+ private ApplicationContext _context;
+
+ public SpringTestCase(Class<? extends SpringConfigFiles> aSpringFiles,
+ Class<? extends HibernateMappingFiles> aMappingFiles) {
+ try {
+ SpringConfigFiles springFiles = aSpringFiles.newInstance();
+ _configLocations = springFiles.toArray(new String[0]);
+ } catch (Exception e) {
+ fail("Could not construct spring config files class '"
+ + aSpringFiles.getName() + "'");
+ }
+
+ // Register the Hibernate mapping files as a bean.
+ _parentContext = new GenericApplicationContext();
+ BeanDefinition lDefinition = new RootBeanDefinition(aMappingFiles);
+ _parentContext.registerBeanDefinition(HIBERNATE_CONFIG_FILES,
+ lDefinition);
+ _parentContext.refresh();
+
+ }
+
+ /**
+ * Gets the spring context.
+ *
+ * @return Spring context.
+ */
+ protected synchronized ApplicationContext getSpringContext() {
+ if (_context == null) {
+ _context = new ClassPathXmlApplicationContext(
+ (String[]) _configLocations, _parentContext);
+ assertNotNull(_context);
+ }
+ return _context;
+ }
+
+ /**
+ * @return Hibernate session factory.
+ */
+ protected SessionFactory getSessionFactory() {
+ SessionFactory factory = (SessionFactory) getSpringContext().getBean(
+ SESSION_FACTORY);
+ assertNotNull(factory);
+ return factory;
+ }
+
+ protected void setUp() throws Exception {
+ LOG.info("Performing setUp()");
+
+ super.setUp();
+
+ _context = null; // make sure we get a new application context for
+ // every
+ // new test.
+
+ BeanKernel.overrideBeanFactory(new TestSpringBeanFactory(
+ getSpringContext()));
+
+ cleanDatabase();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ super.tearDown();
+ } finally {
+ LOG.info("tearDown() complete");
+ }
+ }
+
+ /**
+ * @return Transaction manager
+ */
+ protected PlatformTransactionManager getTransactionManager() {
+ PlatformTransactionManager manager = (PlatformTransactionManager) getSpringContext()
+ .getBean(TRANSACTION_MANAGER);
+ assertNotNull(manager);
+ return manager;
+ }
+
+ /**
+ * @return Starts a new transaction.
+ */
+ protected TransactionStatus getTransaction() {
+ DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+ def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+ return getTransactionManager().getTransaction(def);
+ }
+
+ /**
+ * Returns the hibernate template for executing hibernate-specific
+ * functionality.
+ *
+ * @return Hibernate template.
+ */
+ protected HibernateTemplate getTemplate() {
+ HibernateTemplate template = (HibernateTemplate) getSpringContext()
+ .getBean(HibernateTemplate.class.getName());
+ assertNotNull(template);
+ return template;
+ }
+
+ /**
+ * Flushes the session. Should be called after some Hibernate work and
+ * before JDBC is used to check results.
+ *
+ */
+ protected void flush() {
+ getTemplate().flush();
+ }
+
+ /**
+ * Flushes the session first and then removes all objects from the Session
+ * cache. Should be called after some Hibernate work and before JDBC is used
+ * to check results.
+ *
+ */
+ protected void clear() {
+ flush();
+ getTemplate().clear();
+ }
+
+ /**
+ * Evicts the object from the session. This is essential for the
+ * implementation of unit tests where first an object is saved and is
+ * retrieved later. By removing the object from the session, Hibernate must
+ * retrieve the object again from the database.
+ *
+ * @param aObject
+ */
+ protected void evict(Object aObject) {
+ getTemplate().evict(aObject);
+ }
+
+ /**
+ * Gets the connection.
+ *
+ * @return Connection.
+ */
+ public Connection getConnection() {
+ return DataSourceUtils.getConnection(getDataSource());
+ }
+
+ public void cleanDatabase() throws SQLException {
+
+ if (!isDatabaseConfigured()) {
+ return;
+ }
+
+ String[] tables = getTableNames();
+
+ try {
+ IDatabaseConnection connection = new DatabaseConnection(
+ getConnection());
+ ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
+ IDataSet dataset = new FilteredDataSet(filter, connection
+ .createDataSet(tables));
+
+ DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+ } catch (DatabaseUnitException e) {
+ SQLException exc = new SQLException(e.getMessage());
+ exc.initCause(e);
+ throw exc;
+ }
+ }
+
+ /**
+ * @throws SQLException
+ */
+ public String[] getTableNames() throws SQLException {
+
+ List<String> result = new ArrayList<String>();
+ LOG.debug("Getting database table names to clean (schema: '"
+ + SCHEMA_PATTERN + "'");
+
+ ResultSet tables = getConnection().getMetaData().getTables(null,
+ SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+ while (tables.next()) {
+ String table = tables.getString("TABLE_NAME");
+ // Make sure we do not touch hibernate's specific
+ // infrastructure tables.
+ if (!table.toLowerCase().startsWith("hibernate")) {
+ result.add(table);
+ LOG.debug("Adding " + table
+ + " to list of tables to be cleaned.");
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTables(List aTableList) throws SQLException {
+ Iterator liTable = aTableList.iterator();
+ while (liTable.hasNext()) {
+ emptyTable((String) liTable.next());
+ }
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTable(String aTable) throws SQLException {
+ executeSql("delete from " + aTable);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void dropTable(String aTable) throws SQLException {
+ executeQuery("drop table " + aTable);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction.
+ *
+ * @param aSql
+ * SQL statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql) {
+ return executeSql(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL statement.
+ * @param aArg
+ * Argument of the sql statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql, final Object aArg) {
+ return executeSql(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes an sql statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL query to execute.
+ * @param aArgs
+ * Arguments.
+ * @return Number of rows updated.
+ */
+ public int executeSql(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ JdbcTemplate template = new JdbcTemplate(getDataSource());
+ int result = template.update(aSql, aArgs);
+
+ Map<String, Integer> map = new TreeMap<String, Integer>();
+ map.put("result", new Integer(result));
+
+ return map;
+ }
+ });
+
+ return ((Integer) results.get("result")).intValue();
+ }
+
+ /**
+ * Executes a transaction with a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work.
+ * @return Result.
+ */
+ public Object executeTransaction(TransactionCallback aCallback) {
+ TransactionTemplate lTemplate = new TransactionTemplate(
+ getTransactionManager());
+ return lTemplate.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction without a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work. .
+ */
+ protected void executeTransaction(TransactionCallbackWithoutResult aCallback) {
+ TransactionTemplate template = new TransactionTemplate(
+ getTransactionManager());
+ template.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ * @return Result.
+ */
+ public Map executeTransaction(final TestTransactionCallback aCallback) {
+ return (Map) executeTransaction(new TransactionCallback() {
+ public Object doInTransaction(TransactionStatus aArg) {
+ try {
+ return aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ */
+ public void executeTransaction(
+ final TestTransactionCallbackWithoutResult aCallback) {
+ executeTransaction(new TransactionCallbackWithoutResult() {
+ public void doInTransactionWithoutResult(TransactionStatus aArg) {
+ try {
+ aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes an SQL query within a transaction.
+ *
+ * @param aSql
+ * Query to execute.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql) {
+ return executeQuery(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes a query with a single argument. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Query.
+ * @param aArg
+ * Argument.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql, Object aArg) {
+ return executeQuery(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes a query within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Sql query.
+ * @param aArgs
+ * Arguments to the query.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ Connection connection = getConnection();
+
+ PreparedStatement statement = connection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ ResultSet resultSet = statement.executeQuery();
+ TreeMap results = new TreeMap();
+ results.put("resultSet", resultSet);
+
+ return results;
+ }
+ });
+
+ return (ResultSet) results.get("resultSet");
+ }
+
+ /**
+ * Sets the values of a prepared statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aArgs
+ * Arguments to the prepared statement.
+ * @param aStatement
+ * Prepared statement
+ * @throws SQLException
+ */
+ private void setPreparedParams(final Object[] aArgs,
+ PreparedStatement aStatement) throws SQLException {
+ for (int i = 1; i <= aArgs.length; i++) {
+ setPreparedParam(i, aStatement, aArgs[i - 1]);
+ }
+ }
+
+ /**
+ * Sets a prepared statement parameter.
+ *
+ * @param aIndex
+ * Index of the parameter.
+ * @param aStatement
+ * Prepared statement.
+ * @param aObject
+ * Value Must be of type Integer, Long, or String. TODO extend
+ * with more types of values.
+ * @throws SQLException
+ */
+ private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+ Object aObject) throws SQLException {
+ if (aObject instanceof Integer) {
+ aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+ } else if (aObject instanceof Long) {
+ aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+ } else if (aObject instanceof String) {
+ aStatement.setString(aIndex, (String) aObject);
+ } else {
+ TestCase.fail("Unsupported object type for prepared statement: "
+ + aObject.getClass() + " value: " + aObject
+ + " statement: " + aStatement);
+ }
+ }
+
+ private boolean isDatabaseConfigured() {
+ try {
+ getDataSource();
+ } catch (NoSuchBeanDefinitionException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return Returns the dataSource.
+ */
+ public DataSource getDataSource() {
+ DataSource ds = (DriverManagerDataSource) getSpringContext().getBean(
+ DATA_SOURCE);
+ assertNotNull(ds);
+ return ds;
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ protected int getTableSize(String aTable) throws SQLException {
+ ResultSet resultSet = executeQuery("select * from " + aTable);
+ int count = 0;
+
+ while (resultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+ protected int countResultSet(ResultSet aResultSet) throws SQLException {
+ int count = 0;
+
+ while (aResultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.general.BeanFactoryException;
+
+/**
+ * Bean factory which uses Spring.
+ */
+public class TestSpringBeanFactory implements BeanFactory {
+
+ private ApplicationContext _context;
+
+ public TestSpringBeanFactory(ApplicationContext aContext) {
+ _context = aContext;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String, java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _context.getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import junit.framework.Assert;
+
+/**
+ * @author Erik Test support utility.
+ */
+public final class TestSupport {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TestSupport() {
+ // Empty
+ }
+
+ /**
+ * Obtain root directory of JUnit tests.
+ *
+ * @return Directory name.
+ */
+ public static File getTestRootDir() {
+ return new File("testdata");
+ }
+
+ /**
+ * Returns a temporary directory.
+ *
+ * @return Temporary directory.
+ */
+ public static File getTmpDir() {
+ return new File(getTestRootDir(), "tmpdir");
+ }
+
+ /**
+ * Recursively remove a directory.
+ *
+ * @param aSrc
+ * Directoryto remove.
+ */
+ public static void removeDir(File aSrc) {
+ if (!aSrc.exists()) {
+ return;
+ }
+ Assert.assertTrue(aSrc.getPath(), aSrc.isDirectory());
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ removeDir(file);
+ } else {
+ Assert.assertTrue(file.getPath(), file.delete());
+ }
+ }
+ Assert.assertTrue(aSrc.getPath(), aSrc.delete());
+ }
+
+ /**
+ * Recursively copy a directory.
+ *
+ * @param aSrc
+ * Source directory
+ * @param aTarget
+ * Target directory.
+ */
+ public static void copyDir(File aSrc, File aTarget) {
+ Assert.assertTrue(aSrc.isDirectory());
+ Assert.assertTrue(!aTarget.exists());
+
+ aTarget.mkdirs();
+
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ if (!file.getName().equals(".svn")) {
+ copyDir(new File(aSrc, file.getName()), new File(aTarget,
+ file.getName()));
+ }
+ } else {
+ copyFile(file, new File(aTarget, file.getName()));
+ }
+ }
+ }
+
+ /**
+ * Copy a file. If copying fails then the testcase will fail.
+ *
+ * @param aSrc
+ * Source file.
+ * @param aTarget
+ * Target file.
+ */
+ public static void copyFile(File aSrc, File aTarget) {
+
+ try {
+ FileInputStream fis = new FileInputStream(aSrc);
+ FileOutputStream fos = new FileOutputStream(aTarget);
+ FileChannel fcin = fis.getChannel();
+ FileChannel fcout = fos.getChannel();
+
+ // map input file
+
+ MappedByteBuffer mbb = fcin.map(FileChannel.MapMode.READ_ONLY, 0,
+ fcin.size());
+
+ // do the file copy
+ fcout.write(mbb);
+
+ // finish up
+
+ fcin.close();
+ fcout.close();
+ fis.close();
+ fos.close();
+ } catch (IOException e) {
+ Assert.assertTrue("Copying file " + aSrc.getPath() + " to "
+ + aTarget.getPath() + " failed.", false);
+ }
+ }
+
+ /**
+ * Remove a file or directory. The test case will fail if this does not
+ * succeed.
+ *
+ * @param aFile
+ * entry to remove.
+ */
+ public static void delete(File aFile) {
+ Assert
+ .assertTrue("Could not delete " + aFile.getPath(), aFile
+ .delete());
+ }
+
+ /**
+ * Remove all files within a given directory including the directory itself.
+ * This only attempts to remove regular files and not directories within the
+ * directory. If the directory contains a nested directory, the deletion
+ * will fail. The test case will fail if this fails.
+ *
+ * @param aDir
+ * Directory to remove.
+ */
+ public static void deleteDir(File aDir) {
+ cleanDir(aDir);
+ delete(aDir);
+ }
+
+ /**
+ * Remove all regular files within a given directory.
+ *
+ * @param outputDirName
+ */
+ public static void cleanDir(File aDir) {
+ if (!aDir.exists()) {
+ return; // nothing to do.
+ }
+ File[] entries = aDir.listFiles();
+ for (int i = 0; i < entries.length; i++) {
+ File file = entries[i];
+ if (file.isFile()) {
+ Assert.assertTrue("Could not delete " + entries[i].getPath(),
+ entries[i].delete());
+ }
+ }
+ }
+
+ /**
+ * Creates directory if it does not already exist. The test case will fail
+ * if the directory cannot be created.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.isDirectory()) {
+ return; // nothing to do.
+ }
+ Assert.assertTrue("Could not create directory " + aDir.getPath(), aDir
+ .mkdirs());
+ }
+}
--- /dev/null
+package org.wamblee.test;
+
+import java.util.Map;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ */
+public interface TestTransactionCallback {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @return A map containg the resuls of the execution. This is a convenient
+ * method of returning multiple results from a call.
+ */
+ Map execute() throws Exception;
+}
--- /dev/null
+package org.wamblee.test;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ */
+public interface TestTransactionCallbackWithoutResult {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ */
+ void execute() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import junit.framework.TestCase;
+
+/**
+ * Timing utilities.
+ */
+public final class TimingUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TimingUtils() {
+ // Empty
+ }
+
+ /**
+ * Sleeps for a time.
+ * @param aMillis Number of milliseconds to sleep.
+ */
+ public static void sleep(int aMillis) {
+ try {
+ Thread.sleep(aMillis);
+ } catch (InterruptedException e) {
+ TestCase.fail("Who interrupted my sleep?");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.io.ClassPathResource;
+import org.wamblee.io.FileSystemUtils;
+
+/**
+ * Tests for {@link org.wamblee.xml.ClasspathUriResolver}.
+ */
+public class ClasspathUriResolverTest extends TestCase {
+
+ private URIResolver _resolver;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _resolver = new ClasspathUriResolver();
+ }
+
+ /**
+ * Resolves an existing file. Verifies the file is resolved correctly.
+ * @throws TransformerException
+ * @throws IOException
+ */
+ public void testResolveExistingFile() throws TransformerException, IOException {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml.xsl", "");
+ assertTrue(source instanceof StreamSource);
+ String resolved = FileSystemUtils.read(((StreamSource)source).getInputStream());
+
+ ClassPathResource resource = new ClassPathResource("org/wamblee/xml/reportToHtml.xsl");
+ String expected = FileSystemUtils.read(resource.getInputStream());
+ assertEquals(expected, resolved);
+ }
+
+ /**
+ * Resolves a non-existing file. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testResolveNonExistingFile() {
+ try {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml-nonexisting.xsl", "");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import junit.framework.TestCase;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.Element;
+
+/**
+ * XML test support utilities.
+ */
+public final class XmlUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private XmlUtils() {
+ // Empty
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg,
+ org.w3c.dom.Document aExpected, org.w3c.dom.Document aActual) {
+ assertEquals(aMsg, DomUtils.convert(aExpected), DomUtils
+ .convert(aActual));
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Document aExpected,
+ Document aActual) {
+ assertEquals(aMsg + "/" + aExpected.getRootElement().getName(), aExpected.getRootElement(), aActual.getRootElement());
+ }
+
+ /**
+ * Checks equality of two XML elements excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Element aExpected,
+ Element aActual) {
+
+ // Name.
+ TestCase.assertEquals(aMsg + "/name()", aExpected.getName(), aActual
+ .getName());
+
+ // Text
+ TestCase.assertEquals(aMsg + "/text()", aExpected.getTextTrim(),
+ aActual.getTextTrim());
+
+ // Attributes
+ List<Attribute> expectedAttrs = aExpected.attributes();
+ Collections.sort(expectedAttrs, new AttributeComparator());
+ List<Attribute> actualAttrs = aActual.attributes();
+ Collections.sort(actualAttrs, new AttributeComparator());
+
+ TestCase.assertEquals("count(" + aMsg + "/@*)", expectedAttrs.size(),
+ actualAttrs.size());
+ for (int i = 0; i < expectedAttrs.size(); i++) {
+ String msg = aMsg + "/@" + expectedAttrs.get(i).getName();
+ assertEquals(msg, expectedAttrs.get(i), actualAttrs.get(i));
+ }
+
+ // Nested elements.
+ List<Element> expectedElems = aExpected.elements();
+ List<Element> actualElems = aActual.elements();
+ TestCase.assertEquals("count(" + aMsg + "/*)", expectedElems.size(),
+ actualElems.size());
+ // determine the how-manyth element of the given name we are at.
+ // Maps element name to the last used index (or null if not yet used)
+ Map<String, Integer> elementIndex = new TreeMap<String, Integer>();
+ for (int i = 0; i < expectedElems.size(); i++) {
+ String elemName = expectedElems.get(i).getName();
+ Integer index = elementIndex.get(elemName);
+ if (index == null) {
+ index = 1;
+ } else {
+ index++;
+ }
+ elementIndex.put(elemName, index);
+ String msg = aMsg + "/" + expectedElems.get(i).getName() + "["
+ + index + "]";
+
+ assertEquals(msg, expectedElems.get(i), actualElems.get(i));
+ }
+ }
+
+ /**
+ * Checks equality of two attributes. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Attribute aExpected,
+ Attribute aActual) {
+ TestCase.assertEquals(aMsg + ":name", aExpected.getName(), aActual
+ .getName());
+ TestCase.assertEquals(aMsg + ":value", aExpected.getValue(), aActual
+ .getValue());
+ }
+
+ /**
+ * Comparator which compares attributes by name.
+ */
+ private static final class AttributeComparator implements
+ Comparator<Attribute> {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Comparator#compare(T, T)
+ */
+ public int compare(Attribute aAttribute1, Attribute aAttribute2) {
+ return aAttribute1.getName().compareTo(aAttribute2.getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.io.ClassPathResource;
+import org.w3c.dom.Document;
+import org.wamblee.io.FileSystemUtils;
+import org.wamblee.io.InputResource;
+import org.wamblee.io.TestResource;
+
+/**
+ * Tests the XSL transformer.
+ */
+public class XslTransformerTest extends TestCase {
+
+ private static final String REPORT_XML = "report.xml";
+
+ private static final String REPORT_TO_HTML_XSLT = "reportToHtml.xsl";
+
+ private static final String REPORT_TO_HTML2_XSLT = "reportToHtml2.xsl";
+
+ private static final String REPORT_TO_HTML_INVALID_XSLT = "reportToHtml-invalid.xsl";
+
+ private static final String REPORT_TO_HTML_NONWELLFORMED_XSLT = "reportToHtml-nonwellformed.xsl";
+
+ private static final String REPORT_TO_TEXT_XSLT = "reportToText.xsl";
+
+
+ /**
+ * Transforms a file while using the default resolver, where the included
+ * file can be found. Verifies the transformation is done correctly.
+ *
+ */
+ public void testTransformUsingDefaultResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(
+ new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ Document document = builder.parse(xmlResource.getInputStream());
+ Source documentSource = new StreamSource(xmlResource.getInputStream());
+
+ Document expected = DomUtils.read(new TestResource(
+ XslTransformerTest.class, "output-reportToHtml-report.xml")
+ .getInputStream());
+
+ Document output1 = transformer.transform(documentData, xslt);
+ XmlUtils.assertEquals("byte[] transform", expected, output1);
+
+ Document output2 = transformer.transform(document, xslt);
+ XmlUtils.assertEquals("document transform", expected, output2);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Result output = new StreamResult(os);
+ transformer.transform(documentSource, output, xslt);
+ XmlUtils.assertEquals("document source transform", expected, DomUtils
+ .read(os.toString()));
+
+ String result = transformer.textTransform(documentData, xslt);
+ XmlUtils
+ .assertEquals("text transform", expected, DomUtils.read(result));
+ }
+
+ /**
+ * Transforms a file using the default resolver where the included file
+ * cannot be found. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testTransformUsingDefaultResolverFails() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML2_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using an invalid Xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformInvalidXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML_INVALID_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a non-well formed xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformNonWellformedXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML_NONWELLFORMED_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a class path resolver.
+ *
+ */
+ public void testTransformUsingClassPathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML2_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ Document output1 = transformer.transform(documentData, xslt);
+ Document expected = DomUtils.read(new TestResource(
+ XslTransformerTest.class, "output-reportToHtml-report.xml")
+ .getInputStream());
+ XmlUtils.assertEquals("doc", expected, output1);
+ }
+
+ /**
+ * Transforms a file to text output. Verifies the file is transformed
+ * correctly.
+ *
+ */
+ public void testTransformToTextOutput() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(
+ new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_TEXT_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ String result = transformer.textTransform(documentData, xslt);
+ String expected = "Hello world!";
+ assertEquals("text transform", expected, result);
+ }
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)}.
+ *
+ */
+ public void testResolveWithDefaultResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer();
+ File utilities = new File(FileSystemUtils.getTestInputDir(XslTransformerTest.class), "utilities.xsl");
+ Source source = transformer.resolve(utilities.getAbsolutePath());
+ assert(source instanceof StreamSource);
+ StreamSource ssource = (StreamSource)source;
+ String data = FileSystemUtils.read(ssource.getInputStream());
+ String expected = FileSystemUtils.read(new ClassPathResource("org/wamblee/xml/utilities.xsl").getInputStream());
+ assertEquals(expected, data);
+ }
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver where the file does not exist.
+ *
+ */
+ public void testResolveWithDefaultResolverFileNotFound() {
+ XslTransformer transformer = new XslTransformer();
+ try {
+ Source source = transformer.resolve("org/wamblee/xml/utilities-nonexistent.xsl");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver.
+ *
+ */
+ public void testResolveWithClasspathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+ Source source = transformer.resolve("org/wamblee/xml/utilities.xsl");
+ assert(source instanceof StreamSource);
+ StreamSource ssource = (StreamSource)source;
+ String data = FileSystemUtils.read(ssource.getInputStream());
+ String expected = FileSystemUtils.read(new ClassPathResource("org/wamblee/xml/utilities.xsl").getInputStream());
+ assertEquals(expected, data);
+ }
+
+}
+
+
\ No newline at end of file
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="test"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="test"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org/wamblee/general/spring1.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactoryBla
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactory
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <bean id="java.lang.String"
+ class="java.lang.String">
+ <constructor-arg><value>hello</value></constructor-arg>
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+This is my resource
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <h2>Successfully recorded programs <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:25 - 00:10: <strong>Wintertijd</strong> (Nederland
+ 1/Documentaire)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Some description MINSK - De presidentsverkiezingen
+ in Wit-Rusland zijn zondag met ruime cijfers gewonnen door
+ zittend president Aleksandr Loekasjenko. Dat bleek zondag uit
+ exitpolls uitgevoerd in opdracht van het totalitaire regime. Het
+ staatshoofd zou kunnen rekenen op ruim 82 procent van de
+ stemmen. Volgens de eerste gedeeltelijke uitslagen zou
+ Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p></h2>
+ <h2>Possibly interesting programs</h2>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Brainiac</strong> (Discovery Channel/science)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Humor</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ <h3>Category: horror</h3>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Andere tijden</strong> (Nederland 1/docu)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Documentaire</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ </body>
+</html>
--- /dev/null
+<report>
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifdd test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:ifdd>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifx test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:include href="org/wamblee/xml/utilities.xsl"/>
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:template match="report">
+ <xsl:text>Hello world!</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry output="crawler/basic/build/bin" kind="src" path="crawler/basic/src"/>
+ <classpathentry output="gps/build/bin" kind="src" path="gps/src"/>
+ <classpathentry output="crawler/kissweb/build/testbin" kind="src" path="crawler/kissweb/test"/>
+ <classpathentry output="crawler/kissweb/WebRoot/WEB-INF/classes" kind="src" path="crawler/kissweb/src"/>
+ <classpathentry output="crawler/basic/build/testbin" kind="src" path="crawler/basic/test"/>
+ <classpathentry output="crawler/kiss/build/bin" kind="src" path="crawler/kiss/src"/>
+ <classpathentry output="support/build/bin" kind="src" path="support/src"/>
+ <classpathentry output="support/build/testbin" kind="src" path="support/test"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/external/jtidy-4aug2000r7-dev.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/dbunit-2.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/jmock-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/jmock-cglib-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/basic/lib/test/junit-3.8.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/commons-httpclient-3.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/jtidy-4aug2000r7-dev.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/test/jmock-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/test/jmock-cglib-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/dom4j-1.6.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/ehcache-1.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/log4j-1.2.9.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/antlr-2.7.5H3.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/asm.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/asm-attrs.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/cglib-2.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/hibernate-3.0.5.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/jmock-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/jmock-cglib-1.0.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/jta.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/oro-2.0.6.jar"/>
+ <classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/commons-codec-1.3.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/jaxen-1.1-beta-4.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/activation.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/mail.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/xerces-2.4.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="crawler/kiss/lib/external/commons-email-1.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/emma_ant-2.0.5312.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/test/emma-2.0.5312.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/resources/test"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/commons-beanutils-1.7.0.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/commons-logging-1.0.2.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/jaxen-1.1-beta-4.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/commons-collections-3.1.jar"/>
+ <classpathentry exported="true" kind="lib" path="support/lib/external/spring-1.2.5.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/activation.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-beanutils-1.7.0.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-codec-1.3.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-email-1.0.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-httpclient-3.0.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/commons-logging-1.0.2.jar"/>
+ <classpathentry sourcepath="/usr/java/dom4j/src/java" kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/dom4j-1.6.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/jaxen-1.1-beta-4.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/jstl-1.1.2.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/jtidy-4aug2000r7-dev.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/log4j-1.2.9.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/mail.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/quartz-1.5.1.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/servletapi-2.4.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/spring-1.2.5.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/standard-1.1.2.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/wamblee-crawler-basic.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/wamblee-crawler-kiss.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/wamblee-support.jar"/>
+ <classpathentry kind="lib" path="crawler/kissweb/WebRoot/WEB-INF/lib/xerces-2.4.0.jar"/>
+ <classpathentry kind="lib" path="gps/lib/external/jcommon-1.0.0.jar"/>
+ <classpathentry sourcepath="/usr/java/jfreechart/source" kind="lib" path="gps/lib/external/jfreechart-1.0.1.jar"/>
+ <classpathentry kind="output" path="crawler/basic/build/bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project-module
+ type="WEB"
+ name="utils"
+ id="myeclipse.1145729924792"
+ context-root="/kisscrawler"
+ j2ee-spec="1.4"
+ archive="utils.war">\r
+ <attributes>\r
+ <attribute name="webrootdir" value="/crawler/kissweb/WebRoot" />\r
+ </attributes>\r
+</project-module>\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>utils</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.WebClasspathBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.J2EEProjectValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.DeploymentDescriptorValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.ibm.etools.validation.validationbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.ast.deploy.core.DeploymentBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.ibm.sse.model.structuredbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.genuitec.eclipse.ast.deploy.core.deploymentnature</nature>
+ <nature>com.genuitec.eclipse.j2eedt.core.webnature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+This is the utilities project of wamblee.org. It contains various utilities
+and useful programs for various purposes. The most important part is the
+support library.
+
--- /dev/null
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
--- /dev/null
+Quickstart: Type 'ant dist-lite' to build everything and download dependencies.
+
+You must use a java 5 compiler, jdk 1.4.* or earlier will not work.
+
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:build/header.xml">
+ <!ENTITY delegator SYSTEM "file:build/delegator.xml">
+ <!ENTITY trailer SYSTEM "file:build/trailer.xml">
+]>
+
+<project name="utils" basedir=".">
+
+ <property name="project.home" value="."/>
+
+ &header;
+
+ <property name="projects" value="support,crawler,socketproxy,gps"/>
+
+ &delegator;
+
+</project>
--- /dev/null
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">\r
+<xsl:output method="html" indent="yes"/>\r
+<xsl:decimal-format decimal-separator="." grouping-separator="," />\r
+\r
+<!-- Checkstyle XML Style Sheet by Stephane Bailliez <sbailliez@apache.org> -->\r
+<!-- Part of the Checkstyle distribution found at http://checkstyle.sourceforge.net -->\r
+<!-- Usage (generates checkstyle_report.html): -->\r
+<!-- <checkstyle failonviolation="false" config="${check.config}"> -->\r
+<!-- <fileset dir="${src.dir}" includes="**/*.java"/> -->\r
+<!-- <formatter type="xml" toFile="${doc.dir}/checkstyle_report.xml"/> -->\r
+<!-- </checkstyle> -->\r
+<!-- <style basedir="${doc.dir}" destdir="${doc.dir}" -->\r
+<!-- includes="checkstyle_report.xml" -->\r
+<!-- style="${doc.dir}/checkstyle-noframes.xsl"/> -->\r
+\r
+<xsl:template match="checkstyle">\r
+ <html>\r
+ <head>\r
+ <style type="text/css">\r
+ .bannercell {\r
+ border: 0px;\r
+ padding: 0px;\r
+ }\r
+ body {\r
+ margin-left: 10;\r
+ margin-right: 10;\r
+ font:normal 80% arial,helvetica,sanserif;\r
+ background-color:#FFFFFF;\r
+ color:#000000;\r
+ }\r
+ .a td { \r
+ background: #efefef;\r
+ }\r
+ .b td { \r
+ background: #fff;\r
+ }\r
+ th, td {\r
+ text-align: left;\r
+ vertical-align: top;\r
+ }\r
+ th {\r
+ font-weight:bold;\r
+ background: #ccc;\r
+ color: black;\r
+ }\r
+ table, th, td {\r
+ font-size:100%;\r
+ border: none\r
+ }\r
+ table.log tr td, tr th {\r
+ \r
+ }\r
+ h2 {\r
+ font-weight:bold;\r
+ font-size:140%;\r
+ margin-bottom: 5;\r
+ }\r
+ h3 {\r
+ font-size:100%;\r
+ font-weight:bold;\r
+ background: #525D76;\r
+ color: white;\r
+ text-decoration: none;\r
+ padding: 5px;\r
+ margin-right: 2px;\r
+ margin-left: 2px;\r
+ margin-bottom: 0;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <a name="top"></a>\r
+ <!-- jakarta logo -->\r
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">\r
+ <tr>\r
+ <td class="bannercell" rowspan="2">\r
+ <!--a href="http://jakarta.apache.org/">\r
+ <img src="http://jakarta.apache.org/images/jakarta-logo.gif" alt="http://jakarta.apache.org" align="left" border="0"/>\r
+ </a-->\r
+ </td>\r
+ <td class="text-align:right"><h2>CheckStyle Audit</h2></td>\r
+ </tr>\r
+ <tr>\r
+ <td class="text-align:right">Designed for use with <a href='http://checkstyle.sourceforge.net/'>CheckStyle</a> and <a href='http://jakarta.apache.org'>Ant</a>.</td>\r
+ </tr>\r
+ </table>\r
+ <hr size="1"/>\r
+ \r
+ <!-- Summary part -->\r
+ <xsl:apply-templates select="." mode="summary"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- Package List part -->\r
+ <xsl:apply-templates select="." mode="filelist"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- For each package create its part -->\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:apply-templates select="."/>\r
+ <p/>\r
+ <p/>\r
+ </xsl:for-each>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ \r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+ \r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="filelist"> \r
+ <h3>Files</h3>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Name</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:variable name="errorCount" select="count(error)"/> \r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><a href="#f-{@name}"><xsl:value-of select="@name"/></a></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table> \r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="file">\r
+ <a name="f-{@name}"></a>\r
+ <h3>File <xsl:value-of select="@name"/></h3>\r
+ \r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Error Description</th>\r
+ <th>Line</th>\r
+ </tr>\r
+ <xsl:for-each select="error">\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="@message"/></td>\r
+ <td><xsl:value-of select="@line"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table>\r
+ <a href="#top">Back to top</a>\r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="summary">\r
+ <h3>Summary</h3>\r
+ <xsl:variable name="fileCount" select="count(file)"/>\r
+ <xsl:variable name="errorCount" select="count(file/error)"/>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Files</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="$fileCount"/></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </table>\r
+ </xsl:template>\r
+ \r
+ <xsl:template name="alternated-row">\r
+ <xsl:attribute name="class">\r
+ <xsl:if test="position() mod 2 = 1">a</xsl:if>\r
+ <xsl:if test="position() mod 2 = 0">b</xsl:if>\r
+ </xsl:attribute> \r
+ </xsl:template> \r
+</xsl:stylesheet>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0"?>\r
+\r
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\r
+\r
+<xsl:template match="/">\r
+ <html>\r
+ <head>\r
+ <title>Sun Coding Style Violations</title>\r
+ </head>\r
+ <body bgcolor="#FFFFEF">\r
+ <p><b>Coding Style Check Results</b></p>\r
+ <table border="1" cellspacing="0" cellpadding="2">\r
+ <tr bgcolor="#CC9966">\r
+ <th colspan="2"><b>Summary</b></th>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total files checked</td>\r
+ <td><xsl:number level="any" value="count(descendant::file)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Files with errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::file[error])"/></td>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::error)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Errors per file</td>\r
+ <td><xsl:number level="any" value="count(descendant::error) div count(descendant::file)"/></td>\r
+ </tr>\r
+ </table>\r
+ <hr align="left" width="95%" size="1"/>\r
+ <p>The following are violations of the Sun Coding-Style Standards:</p>\r
+ <p/>\r
+ <xsl:apply-templates/>\r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+\r
+<xsl:template match="file[error]">\r
+ <table bgcolor="#AFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> File: </th>\r
+ <td>\r
+ <xsl:value-of select="@name"/>\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ <table bgcolor="#DFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> Line Number </th>\r
+ <th> Error Message </th>\r
+ </tr>\r
+ <xsl:apply-templates select="error"/>\r
+ </table>\r
+ <p/>\r
+</xsl:template>\r
+\r
+<xsl:template match="error">\r
+ <tr>\r
+ <td>\r
+ <xsl:value-of select="@line"/>\r
+ </td>\r
+ <td>\r
+ <xsl:value-of select="@message"/>\r
+ </td>\r
+ </tr>\r
+</xsl:template>\r
+\r
+</xsl:stylesheet>\r
--- /dev/null
+
+
+ <!-- =============================================================
+ This build file provides delegation to a number of other
+ projects. The projects must be defined in a projects
+ property which contains a comma-separated list of directories
+ to which we must delegate.
+ This build file should be included from another build file
+ which needs to delegate to other targets.
+ ================================================================= -->
+
+ <!-- ================================================================
+ Delegates the build to a number of other projects.
+ Two properties must be set for this to work:
+ - projects: a comma separated list of directories to execute the
+ build in.
+ - targets: List of targets (separated by commas) to execute.
+ ================================================================ -->
+ <target name="delegator" >
+ <for list="${targets}" param="target">
+ <sequential>
+ <for list="${projects}" param="proj">
+ <sequential>
+ <echo>
+=====================================================================
+Executing target '@{target}' for @{proj}
+=====================================================================
+ </echo>
+ <ant dir="@{proj}"
+ inheritAll="false"
+ target="@{target}"/>
+ </sequential>
+ </for>
+ </sequential>
+ </for>
+ </target>
+
+
+
+ <target name="info">
+ <echo>Delegator build file for delegating a task
+ to several other build files. </echo>
+ </target>
+
+ <target name="init_delegator" depends="init_directory_properties,import_header">
+ </target>
+
+
+ <target name="deps" depends="init_delegator" description="download dependencies">
+ <antcall target="delegator">
+ <param name="targets" value="deps"/>
+ </antcall>
+ </target>
+
+ <target name="clean-deps" depends="init_delegator" description="download dependencies">
+ <antcall target="delegator">
+ <param name="targets" value="clean-deps"/>
+ </antcall>
+ </target>
+
+ <target name="clean" depends="init_delegator" description="Clean all targets">
+ <antcall target="delegator">
+ <param name="targets" value="clean"/>
+ </antcall>
+ </target>
+
+ <target name="compile" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="compile,dist-lite-product"/>
+ </antcall>
+ </target>
+
+ <target name="schemaexport" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="schemaexport"/>
+ </antcall>
+ </target>
+
+ <target name="jar" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="jar"/>
+ </antcall>
+ </target>
+
+ <target name="javadoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="javadoc"/>
+ </antcall>
+ </target>
+
+ <target name="testjavadoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="testjavadoc"/>
+ </antcall>
+ </target>
+
+ <target name="pdfdoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="pdfdoc"/>
+ </antcall>
+ </target>
+
+ <target name="doccheck" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="doccheck"/>
+ </antcall>
+ </target>
+
+ <target name="clean-dist" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="clean-dist"/>
+ </antcall>
+ </target>
+
+ <target name="dist-lite" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite"/>
+ </antcall>
+ </target>
+
+ <target name="dist-javadoc" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-javadoc"/>
+ </antcall>
+ </target>
+
+ <target name="dist-lite-product" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-product"/>
+ </antcall>
+ </target>
+
+ <target name="dist-lite-test" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-test"/>
+ </antcall>
+ </target>
+
+
+ <target name="dist" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist"/>
+ </antcall>
+ </target>
+
+ <target name="checkstyle" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="checkstyle"/>
+ </antcall>
+ </target>
+
+ <target name="format" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="format"/>
+ </antcall>
+ </target>
+
+ <target name="simian" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="simian"/>
+ </antcall>
+ </target>
+
+ <target name="macker" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="macker"/>
+ </antcall>
+ </target>
+
+ <target name="testclasses" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="test" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="dist-lite-product,dist-lite-test,test"/>
+ </antcall>
+ </target>
+
+ <target name="junit-reports" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="junit-reports,dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="jcov-reports" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="jcov-reports,dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="reports" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="reports,dist-lite-product,dist-lite-test"/>
+ </antcall>
+ </target>
+
+ <target name="forrest" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="forrest"/>
+ </antcall>
+ </target>
+
+
+ <target name="schemaupdate" depends="init_delegator">
+ <antcall target="delegator">
+ <param name="targets" value="schemaupdate"/>
+ </antcall>
+ </target>
+
+
--- /dev/null
+<!-- PROPERTIES -->
+
+<target name="echoUserPropertiesFileFound" if="user.properties.found">
+ <echo>Using properties file ${user.property.file}</echo>
+</target>
+
+<target name="echoUserPropertiesFileNotFound" unless="user.properties.found">
+ <echo>Properties file ${user.property.file} not found, reverting to defaults</echo>
+</target>
+
+
+<property name="project.home" value=".."/>
+<property name="build.dir" value="${project.home}/build"/>
+<property name="lib.dir" value="${project.home}/lib"/>
+<property environment="env"/>
+
+<target name="download.dep">
+ <if>
+ <isset property="proxyhost"/>
+ <then>
+ <setproxy proxyhost="${proxyhost}" proxyport="${proxyport}"/>
+ </then>
+ </if>
+ <if>
+ <isset property="artifact"/>
+ <then>
+ <echo>Getting dependence ${group}/${artifact}/${version}</echo>
+ <dependencies verbose="true" fileSetId="my.deps">
+ <dependency group="${group}" artifact="${artifact}" version="${version}"/>
+ </dependencies>
+ <copy todir="${download.dir}" flatten="yes">
+ <fileset refid="my.deps"/>
+ </copy>
+ </then>
+ <else>
+ <echo>Getting dependence ${group}/${version}</echo>
+ <dependencies verbose="true" fileSetId="my.deps">
+ <dependency group="${group}" version="${version}"/>
+ </dependencies>
+ <copy todir="${download.dir}" flatten="yes">
+ <fileset refid="my.deps"/>
+ </copy>
+ </else>
+ </if>
+</target>
+
+<!-- LOG4j -->
+<target name="log4j.d">
+ <antcall target="download.dep">
+ <param name="group" value="log4j"/>
+ <param name="version" value="1.2.9"/>
+ </antcall>
+</target>
+
+<target name="commons-beanutils.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-beanutils"/>
+ <param name="version" value="1.7.0"/>
+ </antcall>
+</target>
+
+<target name="commons-collections.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-collections"/>
+ <param name="version" value="3.1"/>
+ </antcall>
+</target>
+
+
+<target name="commons-logging.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-logging"/>
+ <param name="artifact" value="commons-logging"/>
+ <param name="version" value="1.0.2"/>
+ </antcall>
+</target>
+
+<target name="commons-codec.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-codec"/>
+ <param name="version" value="1.3"/>
+ </antcall>
+</target>
+
+<target name="commons-email.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-email"/>
+ <param name="version" value="1.0"/>
+ </antcall>
+</target>
+
+<target name="servletapi.d">
+ <antcall target="download.dep">
+ <param name="group" value="servletapi"/>
+ <param name="version" value="2.4"/>
+ </antcall>
+</target>
+
+<target name="jstl.d">
+ <antcall target="download.dep">
+ <param name="group" value="jstl"/>
+ <param name="version" value="1.1.2"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="taglibs"/>
+ <param name="artifact" value="standard"/>
+ <param name="version" value="1.1.2"/>
+ </antcall>
+</target>
+
+
+
+<target name="quartz.d">
+ <antcall target="download.dep">
+ <param name="group" value="quartz"/>
+ <param name="version" value="1.5.1"/>
+ </antcall>
+</target>
+
+
+
+<target name="logging.d" depends="log4j.d,commons-logging.d">
+</target>
+
+<target name="dom4j.d">
+ <antcall target="download.dep">
+ <param name="group" value="dom4j"/>
+ <param name="version" value="1.6"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="jaxen"/>
+ <param name="version" value="1.1-beta-4"/>
+ </antcall>
+</target>
+
+<target name="jfreechart.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/jfreechart-1.0.1">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="ehcache.d">
+ <antcall target="download.dep">
+ <param name="group" value="ehcache"/>
+ <param name="version" value="1.1"/>
+ </antcall>
+</target>
+
+<target name="xerces.d">
+ <antcall target="download.dep">
+ <param name="group" value="xerces"/>
+ <param name="version" value="2.4.0"/>
+ </antcall>
+</target>
+
+<target name="oro.d">
+ <antcall target="download.dep">
+ <param name="group" value="oro"/>
+ <param name="version" value="2.0.6"/>
+ </antcall>
+</target>
+
+<target name="cglib.d">
+ <antcall target="download.dep">
+ <param name="group" value="cglib"/>
+ <param name="version" value="2.1"/>
+ </antcall>
+</target>
+
+<target name="hibernate.d" depends="cglib.d,oro.d">
+ <antcall target="download.dep">
+ <param name="group" value="hibernate"/>
+ <param name="version" value="3.0.5"/>
+ </antcall>
+</target>
+
+<target name="hibernate.standalone.d" depends="hibernate.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/hibernate">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="spring.d">
+ <antcall target="download.dep">
+ <param name="group" value="springframework"/>
+ <param name="artifact" value="spring"/>
+ <param name="version" value="1.2.5"/>
+ </antcall>
+</target>
+
+<target name="activation.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/jaf-1.0.2">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="mail.d" depends="activation.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/javamail-1.3.3_01">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+
+
+<target name="httpclient.d">
+ <antcall target="download.dep">
+ <param name="group" value="commons-httpclient"/>
+ <param name="version" value="3.0"/>
+ </antcall>
+</target>
+
+<target name="jtidy.d">
+ <antcall target="download.dep">
+ <param name="group" value="jtidy"/>
+ <param name="version" value="4aug2000r7-dev"/>
+ </antcall>
+</target>
+
+<property name="support.dist.dir" value="${lib.dir}/wamblee/support"/>
+<target name="wamblee.support.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${support.dist.dir}">
+ <include name="wamblee-support.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.support.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${support.dist.dir}">
+ <include name="wamblee-support-test.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+
+<target name="junit.d">
+ <antcall target="download.dep">
+ <param name="group" value="junit"/>
+ <param name="version" value="3.8.1"/>
+ </antcall>
+ <copy todir="${download.dir}">
+ <fileset dir="${special.lib.dir}/test">
+ <include name="*.jar"/>
+ </fileset>
+ </copy>
+</target>
+
+<target name="emma.d">
+ <antcall target="download.dep">
+ <param name="group" value="emma"/>
+ <param name="artifact" value="emma"/>
+ <param name="version" value="2.0.5312"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="emma"/>
+ <param name="artifact" value="emma_ant"/>
+ <param name="version" value="2.0.5312"/>
+ </antcall>
+</target>
+
+<target name="jmock.d">
+ <antcall target="download.dep">
+ <param name="group" value="jmock"/>
+ <param name="artifact" value="jmock"/>
+ <param name="version" value="1.0.1"/>
+ </antcall>
+ <antcall target="download.dep">
+ <param name="group" value="jmock"/>
+ <param name="artifact" value="jmock-cglib"/>
+ <param name="version" value="1.0.1"/>
+ </antcall>
+</target>
+
+<target name="dbunit.d">
+ <antcall target="download.dep">
+ <param name="group" value="dbunit"/>
+ <param name="version" value="2.1"/>
+ </antcall>
+</target>
+
+
+<target name="antlr.d">
+ <antcall target="download.dep">
+ <param name="group" value="antlr"/>
+ <param name="version" value="2.7.2"/>
+ </antcall>
+</target>
+
+<target name="checkstyle.d" depends="antlr.d,commons-beanutils.d,commons-logging.d,commons-collections.d">
+ <antcall target="download.dep">
+ <param name="group" value="checkstyle"/>
+ <param name="version" value="4.1"/>
+ </antcall>
+</target>
+
+<!-- common test dependencies for all test code -->
+<target name="test.d" depends="junit.d,jmock.d,dbunit.d,emma.d">
+</target>
+
+<!-- downloaded dependencies for ant tasks -->
+<target name="ant.d" depends="checkstyle.d">
+</target>
+
+
+
+ <target name="import_header" unless="build_header_included">
+
+ <property name="ant.lib.dir" value="${build.dir}/lib/ant"/>
+ <property name="test.lib.dir" value="lib/test"/>
+ <property name="special.lib.dir" value="${build.dir}/lib/special"/>
+ <property name="ant.downloaded.lib.dir" value="${build.dir}/lib/ant/downloaded"/>
+
+
+ <!-- ========================================================================================
+ Ant task defs
+ ======================================================================================== -->
+
+ <!-- ant-contrib integration -->
+ <property name="ant.contrib.home" value="${ant.lib.dir}/ant-contrib-1.0b2"/>
+ <taskdef resource="net/sf/antcontrib/antlib.xml">
+ <classpath>
+ <pathelement location="${ant.contrib.home}/ant-contrib.jar" />
+ </classpath>
+ </taskdef>
+
+ <!-- taskdef for ant-dependencies task -->
+ <taskdef name="dependencies" classpath="${ant.lib.dir}/ant-dependencies.jar"
+ classname="org.apache.tools.ant.taskdefs.optional.dependencies.Dependencies"/>
+
+ <!-- Emma integration -->
+ <if>
+ <available file="${test.lib.dir}"/>
+ <then>
+ <path id="emma.lib">
+ <fileset dir="${test.lib.dir}" includes="emma*.jar"/>
+ </path>
+ <taskdef resource="emma_ant.properties" classpathref="emma.lib" />
+ <property name="emma.enabled" value="false"/>
+ </then>
+ </if>
+
+ <!-- checkstyle -->
+ <property name="checkstyle.home" value="${ant.downloaded.lib.dir}"/>
+ <taskdef resource="checkstyletask.properties">
+ <classpath>
+ <fileset dir="${checkstyle.home}">
+ <include name="*.jar"/>
+ </fileset>
+ </classpath>
+ </taskdef>
+ <property name="checkstyle.rules" value="style.xml"/>
+ <property name="checkstyle.test.rules" value="test-style.xml"/>
+
+
+ <!-- jalopy -->
+ <!-- TMP download this dependence as well
+ <property name="jalopy.home" value="${lib.dir}/jalopy-ant-0.6.2"/>
+ <property name="jalopy.rules" value="${build.dir}/formatting-rules.xml"/>
+ <taskdef name="jalopy"
+ classname="de.hunsicker.jalopy.plugin.ant.AntPlugin">
+ <classpath>
+ <fileset dir="${jalopy.home}">
+ <include name="*.jar" />
+ </fileset>
+ </classpath>
+ </taskdef>
+ -->
+
+ <!-- simian integration -->
+ <!-- TMP download this dependence as well
+ <property name="simian.home" value="${lib.dir}/simian"/>
+ <taskdef resource="simiantask.properties" classpath="${simian.home}/simian.jar"/>
+ -->
+
+ <!-- macker integration -->
+ <!-- TMP download this dependence as well
+ <property name="macker.home" value="${lib.dir}/macker-0.4.2"/>
+ <path id="macker.path">
+ <fileset dir="${macker.home}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+ <taskdef name="macker"
+ classname="net.innig.macker.ant.MackerAntTask"
+ classpathref="macker.path"/>
+
+ -->
+
+ <!-- ========================================================================================
+ Locate user properties to (optionally) override defaults
+ ======================================================================================== -->
+
+ <!--
+ Give user a chance to override without editing this file
+ (and without typing -D each time they run it).
+
+ The following properties must be defined in this file:
+ - jcoverage.home: root of the jcoverage installation.
+ - jalopy.home: root of the jalopy installation.
+ -->
+ <property name="user.property.file" value="${user.home}/wamblee.properties"/>
+ <available file="${user.property.file}" property="user.properties.found"/>
+ <property file="${user.property.file}"/>
+
+ <antcall target="echoUserPropertiesFileFound"/>
+ <antcall target="echoUserPropertiesFileNotFound"/>
+
+ <!-- project properties defaults, can be overriden in the user's properties file -->
+
+ <!-- The project.home property can also be overriden in a build.xml
+ in case the source is in a subdirectory and not necessarily
+ in the top-level directory -->
+
+
+ <if>
+ <isset property="webroot.dir"/>
+ <then>
+ <property name="external.lib.dir" value="${webroot.dir}/WEB-INF/lib"/>
+ </then>
+ <else>
+ <property name="external.lib.dir" value="lib/external"/>
+ </else>
+ </if>
+
+ <property name="build.properties.dir" value="${project.home}" />
+ <property name="build.properties.name" value="build.properties" />
+
+ <mkdir dir="${external.lib.dir}"/>
+ <mkdir dir="${test.lib.dir}"/>
+
+ <property name="conf.dir" value="conf"/>
+ <property name="log4j.properties.dir" value="${conf.dir}/properties"/>
+
+ <!-- Compilation properties -->
+ <property name="javac.debug" value="true"/>
+ <property name="javac.debug.level" value="lines,vars,source"/>
+ <property name="javac.source" value="1.5"/>
+
+ <!-- ========================================================================================
+ Standard path definitions to be used with all projects
+ ======================================================================================= -->
+ <property name="xmlschemas.dir" value="${conf.dir}/schemas"/>
+ <path id="xmlschemas.path">
+ <pathelement location="${xmlschemas.dir}"/>
+ </path>
+
+ <!-- ========================================================================================
+ Path definitions for external libraries.
+ ======================================================================================== -->
+
+ <!-- Ignore system classpath! -->
+ <property name="build.sysclasspath" value="ignore" />
+
+ <!-- JUnit -->
+ <property name="junit.halt.on.failure" value="false"/>
+ <property name="unitreport" value="cl-unit.xml"/>
+
+ <!-- DocCheck path -->
+ <property name="doccheck.home" value="${ant.lib.dir}/doccheck1.2b2/doccheck.jar"/>
+ <path id="doccheck.path">
+ <pathelement location="${doccheck.home}"/>
+ </path>
+
+ <!-- PdfDoclet path -->
+ <property name="pdfdoclet.home" value="${ant.lib.dir}/pdfdoclet-1.0.2-all.jar"/>
+ <path id="pdfdoclet.path">
+ <pathelement location="${pdfdoclet.home}"/>
+ </path>
+
+ <!-- Hibernate paths -->
+ <!-- name of the file in the source directory containing
+ the names of hibernate files to look at in the correct order -->
+ <property name="hibernate.home" value="${lib.dir}/hibernate-3.0"/>
+ <path id="hibernate.basic.path">
+ <fileset dir="${hibernate.home}">
+ <include name="*.jar"/>
+ </fileset>
+ <path refid="xerces.path"/>
+ <path refid="dom4j.path"/>
+ </path>
+ <path id="hibernate.appserver.path">
+ <path refid="hibernate.basic.path"/>
+ <fileset dir="${hibernate.home}/appserver">
+ <include name="*.jar"/>
+ </fileset>
+ </path>
+ <path id="hibernate.standalone.path">
+ <path refid="hibernate.basic.path"/>
+ <fileset dir="${hibernate.home}/standalone">
+ <include name="*.jar"/>
+ </fileset>
+ </path>
+
+ <!-- Commented the inclusion of a system-wide path in the user's environment into a
+ classpath. System wide classpaths should not be used. -->
+ <!-- property name="classpath_id" value="build.path" / -->
+ <!-- property name="classpath" refid="${classpath_id}"/ -->
+ <property name="classpath" value=""/>
+
+
+ <!-- ========================================================================================
+ Database settings
+ ======================================================================================== -->
+ <property name="database" value="Derby"/>
+
+
+
+ <!-- ========================================================================================
+ Information
+ ======================================================================================== -->
+ <echo level="debug" message="project.home=${project.home}"/>
+ <echo level="debug" message="project.libs=${project.libs}"/>
+ <echo level="debug" message="classpath=${classpath}"/>
+
+
+ <!-- ========================================================================================
+ Included marker
+ ======================================================================================== -->
+ <property name="build_header_included" value="true" />
+</target>
+
+<!-- =================================================================
+ Sets up the directory definitions for the module
+ ================================================================= -->
+<target name="init_directory_properties" depends="import_header">
+ <!-- Set module properties for this build -->
+
+ <property name="module.home" value="." />
+
+ <!-- Replace the dash by a / to create a relative directory
+ from a module name -->
+ <propertyregex property="module.reldir"
+ input="${module.name}"
+ regexp="-"
+ replace="/"
+ global="true"
+ defaultValue="${module.name}"/>
+ <!-- property name="module.build.dir"
+ value="${build.dir}/${module.reldir}" / -->
+ <property name="module.build.dir" value="build"/>
+ <property name="module.api.forrest.dir" value="${forrest.build.site.dir}/api/${module.name}" />
+ <if>
+ <isset property="webroot.dir"/>
+ <then>
+ <property name="module.classes.dir" value="${webroot.dir}/WEB-INF/classes" />
+ </then>
+ <else>
+ <property name="module.classes.dir" value="${module.build.dir}/bin" />
+ </else>
+ </if>
+
+ <property name="module.testclasses.dir" value="${module.build.dir}/testbin" />
+ <!-- Directory where generated SQL will be put by the schema export-->
+ <property name="module.sql.dir" value="${module.build.dir}/sql" />
+
+
+ <property name="module.jcovclasses.dir"
+ value="${module.build.dir}/testjcov" />
+ <property name="module.jars.dir" value="${module.build.dir}/jars" />
+ <property name="module.jar.file"
+ value="${module.jars.dir}/${module.jar.name}" />
+ <property name="module.source.dir" value="${module.home}/src" />
+ <property name="module.forrest.src.dir" value="${module.home}/xdocs" />
+ <property name="module.ejbsource.dir" value="${module.home}/ejbsrc" />
+ <property name="module.test.dir" value="${module.home}/test" />
+ <property name="module.testjar.name"
+ value="${module.name}-test.jar" />
+ <property name="module.testresourcesjar.name"
+ value="${module.name}-test-resources.jar" />
+ <property name="module.resource.dir"
+ value="${module.home}/resources" />
+ <property name="module.testresource.dir"
+ value="${module.resource.dir}/test" />
+ <property name="module.testoutput.dir"
+ value="${module.resource.dir}/testoutput"/>
+ <property name="module.report.dir"
+ value="${module.build.dir}/testresults" />
+ <property name="module.emmareport.dir"
+ value="${module.report.dir}/html/emma"/>
+ <property name="module.docbase.dir"
+ value="${module.build.dir}/docs" />
+ <property name="module.javadoc.dir"
+ value="${module.docbase.dir}/api" />
+ <property name="module.javadocjar.name"
+ value="${module.name}-doc.jar" />
+ <property name="module.testjavadocjar.name"
+ value="${module.name}-test-doc.jar" />
+ <property name="module.testjavadoc.dir"
+ value="${module.docbase.dir}/testapi" />
+ <property name="module.pdfdoc.dir"
+ value="${module.docbase.dir}/pdf" />
+ <property name="module.doccheck.dir"
+ value="${module.docbase.dir}/doccheck" />
+ <property name="module.checkstyle.dir"
+ value="${module.docbase.dir}/checkstyle" />
+ <property name="module.jar.name" value="${module.name}.jar" />
+
+ <!-- Replace the dash by a / to create a relative directory
+ from a module name -->
+ <property name="module.dist.dir" value="${lib.dir}/${module.reldir}" />
+</target>
+
+<!-- ============================================================================
+ Initialize the environment for other tasks. Normally, every task which
+ does not depend on any other task should at least depend on init.
+ ============================================================================ -->
+
+ <target name="help" depends="import_header">
+ <antcall target="help-within-module"/>
+ <antcall target="help-outside-module"/>
+ </target>
+
+ <target name="help-within-module" depends="init_directory_properties" if="module.name">
+ <antcall target="help-impl"/>
+ </target>
+
+ <target name="help-outside-module" unless="module.name">
+ <antcall target="help-outside-module-2">
+ <param name="module.name" value="MODULE_NAME"/>
+ </antcall>
+ </target>
+
+ <target name="help-outside-module-2" depends="init_directory_properties">
+ <antcall target="help-impl"/>
+ </target>
+
+
+ <target name="help-impl">
+ <echo>
+
+ Preparation:
+
+ To execute with a specific proxy host and port, start ant with the
+ command-line options -Dproxyhost=hostname -Dproxyport=portnumber.
+
+ deps: Download dependencies, this is necessary for using
+ any of the build targets.
+ clean-deps: Remove downloaded dependencies.
+
+ General build targets:
+
+ clean: Cleans up all build results (excluding the dist location).
+ compile: Compiles all java clasess. Target dir ${module.classes.dir}
+ jar: Creates a jar file. Target: ${module.jar.file}
+ dist: Makes the build results available for other modules Target: ${module.dist.dir}.
+ dist-lite: Similar to dist but only creates the jars for code and test code. Useful
+ during development because it takes much less time than the dist
+ target
+ clean-dist: Cleans up the dist directories.
+
+ Test:
+
+ test: Compile and run tests.
+ junit-reports: As test but generates a test report. Target: ${module.report.dir}(/html/unit)
+ reports: As junit-reports but also generates an emma test coverage
+ report
+
+ Note: By specifying -Dtest=TestCaseName on the ant command line,
+ only the specified testcase will be run.
+
+ Emma can also be executed manually:
+
+ emma: Overwrites the production classes by their instrumented versions.
+ emma-reports: Generates emma code coverage reports after running the
+ testcases with instrumented classes.
+
+ Javadoc targets:
+
+ javadoc: Generates javadoc. Target: ${module.javadoc.dir}
+ doccheck: Checks documentation. Target: ${module.doccheck.dir}
+
+ Forrest:
+
+ forrest: Generates project docs using forrest.
+
+ Code analysis:
+
+ checkstyle: Checks the style of the code.
+ format: Formats to the code in accordance with the checkstyle rules.
+ simian: Analyse similarities in the code.
+
+ Building a web application:
+
+ To build a web application archive (WAR) you must set the property
+ webroot.dir to the value of the directory which is the root of your
+ web application. This must be done before the header.xl is
+ sourced. The build support will then make sure that
+ classes and libs are put in the correct directories below
+ ${webroot.dir}/WEB-INF, and it will build a web archive.
+
+ Database targets: ** database targets are not functional yet **
+
+ For all targets below the database is configured using
+ hibernate.properties (hibernate.dialect property) and
+ the JDBC connection properties in database.properties.
+
+ The schemaexport and schemaupdate targets require the
+ setting of a property named hibernate.filelist. The value
+ of the property must be the fully qualified class name of
+ a concrete subclass of ConfigFileList which has a default
+ public constructor and defines the Hibernate mapping files that
+ can be used.
+
+ schemaexport: Generate SQL code to create the required database structures.
+
+ startdb: Startup a lightweight database. The database
+ type to start is obtained from the currently
+ configured database.
+
+ schemaupdate: Populate the database with a schema by running
+ Hibernate schemaupdate against the currently
+ configured database.
+ </echo>
+ </target>
+
+
--- /dev/null
+Ant-Contrib library
+
+This library is for contributed Ant tasks that have
+not been approved for inclusion into the ant core or
+optional library.
+
+Requirements
+-------------------------
+Runtime:
+ Requires APACHE Ant Version 1.5 or above. Note, that output
+ handlers on the ForEach task will not properly report the
+ task which is outputting the message unless you are using
+ Ant version 1.5.2 or greater.
+
+ In addition, the <for> task will not work on versions prior
+ to Ant 1.6
+
+Compilation:
+ Requires Ant Version 1.6 or greater to compile and build the
+ package.
+
+
+Inclusion in your project
+-------------------------
+ The easiest way to use the tasks is to use
+
+<taskdef resource="net/sf/antcontrib/antlib.xml">
+ <classpath>
+ <pathelement location="your/path/to/ant-contrib-${version}.jar" />
+ </classpath>
+</taskdef>
+
+in your build file. If the jar file is on your CLASSPATH or in
+ANT_HOME/lib you can even simplify this to read
+
+<taskdef resource="net/sf/antcontrib/antlib.xml" />
+
+For projects which will run under 1.5 versions, you would
+use the .properties file instead of the antlib.xml file:
+
+<taskdef resource="net/sf/antcontrib/antcontrib.properties">
+ <classpath>
+ <pathelement location="your/path/to/ant-contrib.jar" />
+ </classpath>
+</taskdef>
+
--- /dev/null
+ Sun Microsystems, Inc.
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED
+SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY
+"AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE
+MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA
+PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT.
+IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY,
+INDICATE YOUR ACCEPTANCE OF THESE TERMS BY
+SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS
+AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE
+TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR
+PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE
+IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE"
+BUTTON AT THE END OF THIS AGREEMENT.
+
+1. LICENSE TO USE. Sun grants you a
+non-exclusive and non-transferable license for the
+internal use only of the accompanying software and
+documentation and any error corrections provided
+by Sun (collectively "Software"), by the number of
+users and the class of computer hardware for which
+the corresponding fee has been paid.
+
+2. RESTRICTIONS Software is confidential and
+copyrighted. Title to Software and all associated
+intellectual property rights is retained by Sun
+and/or its licensors. Except as specifically
+authorized in any Supplemental License Terms, you
+may not make copies of Software, other than a
+single copy of Software for archival purposes.
+Unless enforcement is prohibited by applicable
+law, you may not modify, decompile, or reverse
+engineer Software. Software is not designed or
+licensed for use in on-line control of aircraft,
+air traffic, aircraft navigation or aircraft
+communications; or in the design, construction,
+operation or maintenance of any nuclear facility.
+No right, title or interest in or to any
+trademark, service mark, logo or trade name of Sun
+or its licensors is granted under this Agreement.
+
+3. LIMITED WARRANTY. Sun warrants to you that for
+a period of ninety (90) days from the date of
+purchase, as evidenced by a copy of the receipt,
+the media on which Software is furnished (if any)
+will be free of defects in materials and
+workmanship under normal use. Except for the
+foregoing, Software is provided "AS IS". Your
+exclusive remedy and Sun's entire liability under
+this limited warranty will be at Sun's option to
+replace Software media or refund the fee paid for
+Software.
+
+4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN
+THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS,
+REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
+IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE
+DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE
+DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
+
+5. LIMITATION OF LIABILITY. TO THE EXTENT NOT
+PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS
+LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
+OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL,
+INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
+REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT
+OF OR RELATED TO THE USE OF OR INABILITY TO USE
+SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES. In no event will
+Sun's liability to you, whether in contract, tort
+(including negligence), or otherwise, exceed the
+amount paid by you for Software under this
+Agreement. The foregoing limitations will apply
+even if the above stated warranty fails of its
+essential purpose.
+
+6. Termination. This Agreement is effective
+until terminated. You may terminate this
+Agreement at any time by destroying all copies of
+Software. This Agreement will terminate
+immediately without notice from Sun if you fail to
+comply with any provision of this Agreement. Upon
+Termination, you must destroy all copies of
+Software.
+
+7. Export Regulations. All Software and
+technical data delivered under this Agreement are
+subject to US export control laws and may be
+subject to export or import regulations in other
+countries. You agree to comply strictly with all
+such laws and regulations and acknowledge that you
+have the responsibility to obtain such licenses to
+export, re-export, or import as may be required
+after delivery to you.
+
+8. U.S. Government Restricted Rights. If
+Software is being acquired by or on behalf of the
+U.S. Government or by a U.S. Government prime
+contractor or subcontractor (at any tier), then
+the Government's rights in Software and
+accompanying documentation will be only as set
+forth in this Agreement; this is in accordance
+with 48 CFR 227.7201 through 227.7202-4 (for
+Department of Defense (DOD) acquisitions) and with
+48 CFR 2.101 and 12.212 (for non-DOD
+acquisitions).
+
+9. Governing Law. Any action related to this
+Agreement will be governed by California law and
+controlling U.S. federal law. No choice of law
+rules of any jurisdiction will apply.
+
+10. Severability. If any provision of this
+Agreement is held to be unenforceable, this
+Agreement will remain in effect with the provision
+omitted, unless omission would frustrate the
+intent of the parties, in which case this
+Agreement will immediately terminate.
+
+11. Integration. This Agreement is the entire
+agreement between you and Sun relating to its
+subject matter. It supersedes all prior or
+contemporaneous oral or written communications,
+proposals, representations and warranties and
+prevails over any conflicting or additional terms
+of any quote, order, acknowledgment, or other
+communication between the parties relating to its
+subject matter during the term of this Agreement.
+No modification of this Agreement will be binding,
+unless in writing and signed by an authorized
+representative of each party.
+
+For inquiries please contact: Sun Microsystems,
+Inc. 901 San Antonio Road, Palo Alto, California
+94303
+
+
+SUPPLEMENTAL LICENSE TERMS
+JDBCTM 2.0 INTERFACE CLASSES
+
+These supplemental license terms ("Supplement")
+add to or modify the terms of the Binary Code
+License Agreement (collectively, the
+"Agreement"). Capitalized terms not defined in
+this Supplement shall have the same meanings
+ascribed to them in the Agreement. These
+Supplement terms shall supersede any inconsistent
+or conflicting terms in the Agreement, or in any
+license contained within the Software.
+
+1. License to Distribute. Sun grants you a
+non-exclusive, non-transferable, limited license
+to reproduce and distribute the binary and/or
+source code form of the Software to third party
+end users through multiple tiers of distribution,
+provided that you: (i) distribute the Software
+complete and unmodified in its original Java
+Archive file, and only bundled as a part of your
+program that incorporates the Software
+("Program"); (ii) do not distribute additional
+software intended to replace any component(s) of
+the Software; (iii) agree to incorporate the most
+current version of the Software that was available
+from Sun no later than 180 days prior to each
+production release of the Program; (iv) do not
+remove or alter any proprietary legends or notices
+contained in or on the Software; (v) only
+distribute the Program pursuant to a license
+agreement that protects Sun's interest consistent
+with the terms contained in the Agreement; (vi)
+may not create, or authorize your licensees to cr!
+eate additional classes, interfaces, or
+subpackages that are contained in the "java"
+"javax" or "sun" packages or similar as specified
+by Sun in any class file naming convention; and
+(vii) agree to defend and indemnify Sun and its
+licensors from and against any damages, costs,
+liabilities, settlement amounts and/or expenses
+(including attorneys' fees) incurred in connection
+with any claim, lawsuit or action by any third
+party that arises or results from the use or
+distribution of any and all Programs.
+
+2. Trademarks and Logos. You acknowledge as
+between you and Sun that Sun owns the Java
+trademark and all Java-related trademarks, logos
+and icons including the Coffee Cup and Duke ("Java
+Marks") and agree to comply with the Java
+Trademark Guidelines at
+http://java.sun.com/trademarks.html.
+
+
+
+
+
+
\ No newline at end of file
--- /dev/null
+
+Sun Microsystems, Inc.
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE" BUTTON AT THE END OF THIS AGREEMENT.
+
+1. LICENSE TO USE. Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively "Software"), by the number of users and the class of computer hardware for which the corresponding fee has been paid.
+
+2. RESTRICTIONS. Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes. Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software. Licensee acknowledges that Licensed Software is not designed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun Microsystems, Inc. disclaims any express or implied warranty of fitness for such uses. No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement.
+
+3. LIMITED WARRANTY. Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided "AS IS". Your exclusive remedy and Sun's entire liability under this limited warranty will be at Sun's option to replace Software media or refund the fee paid for Software.
+
+4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.
+
+5. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Sun's liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose.
+
+6. Termination. This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement. Upon Termination, you must destroy all copies of Software.
+
+7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you.
+
+8. U.S. Government Restricted Rights. If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Government's rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions).
+
+9. Governing Law. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply.
+
+10. Severability. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate.
+
+11. Integration. This Agreement is the entire agreement between you and Sun relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party.
+
+JAVATM INTERFACE CLASSES
+JAVA TRANSACTION API (JTA), VERSION 1.0.1B, MAINTENANCE RELEASE
+SUPPLEMENTAL LICENSE TERMS
+
+These supplemental license terms ("Supplemental Terms") add to or modify the terms of the Binary Code License Agreement (collectively, the "Agreement"). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software.
+
+1. Software Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement, including, but not limited to Section 3 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce internally and use internally the binary form of the Software, complete and unmodified, for the sole purpose of designing, developing and testing your Java applets and applications ("Programs").
+
+2. License to Distribute Software. In addition to the license granted in Section 1 (Software Internal Use and Development License Grant) of these Supplemental Terms, subject to the terms and conditions of this Agreement, including but not limited to Section 3 (Java Technology Restrictions), Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute the Software in binary form only, provided that you (i) distribute the Software complete and unmodified and only bundled as part of your Programs, (ii) do not distribute additional software intended to replace any component(s) of the Software, (iii) do not remove or alter any proprietary legends or notices contained in the Software, (iv) only distribute the Software subject to a license agreement that protects Sun's interests consistent with the terms contained in this Agreement, and (v) agree to defend and indemnify Sun and its licensors from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys' fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software.
+
+3. Java Technology Restrictions. You may not modify the Java Platform Interface ("JPI", identified as classes contained within the "java" package or any subpackages of the "java" package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI. In the event that you create an additional class and associated API(s) which (i) extends the functionality of the Java Platform, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar convention as specified by Sun in any naming convention designation.
+
+4. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET trademarks and all SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET-related trademarks, service marks, logos and other brand designations ("Sun Marks"), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http://www.sun.com/policies/trademarks. Any use you make of the Sun Marks inures to Sun's benefit.
+
+5. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement. Source code may not be redistributed unless expressly provided for in this Agreement.
+
+6. Termination for Infringement. Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right.
+
+For inquiries please contact: Sun Microsystems, Inc. 4150 Network Circle, Santa Clara, California 95054.
+
+
--- /dev/null
+<?xml version="1.0"?>\r
+<!DOCTYPE module PUBLIC\r
+ "-//Puppy Crawl//DTD Check Configuration 1.2//EN"\r
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">\r
+\r
+<!--\r
+\r
+ Checkstyle configuration that checks the sun coding conventions from:\r
+\r
+ - the Java Language Specification at\r
+ http://java.sun.com/docs/books/jls/second_edition/html/index.html\r
+\r
+ - the Sun Code Conventions at http://java.sun.com/docs/codeconv/\r
+\r
+ - the Javadoc guidelines at\r
+ http://java.sun.com/j2se/javadoc/writingdoccomments/index.html\r
+\r
+ - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html\r
+\r
+ - some best practices\r
+\r
+ Checkstyle is very configurable. Be sure to read the documentation at\r
+ http://checkstyle.sf.net (or in your downloaded distribution).\r
+\r
+ Most Checks are configurable, be sure to consult the documentation.\r
+\r
+ To completely disable a check, just comment it out or delete it from the file.\r
+\r
+ Finally, it is worth reading the documentation.\r
+\r
+-->\r
+\r
+<module name="Checker">\r
+\r
+ <!-- Checks that a package.html file exists for each package. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->\r
+ <!-- module name="PackageHtml"/ -->\r
+\r
+ <!-- Checks whether files end with a new line. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->\r
+ <module name="NewlineAtEndOfFile"/>\r
+\r
+ <!-- Checks that property files contain the same keys. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->\r
+ <module name="Translation"/>\r
+\r
+\r
+ <module name="TreeWalker">\r
+\r
+ <!-- Checks for Javadoc comments. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html -->\r
+ <!-- module name="JavadocMethod"/ -->\r
+ <!-- module name="JavadocType"/ -->\r
+ <!-- module name="JavadocVariable"/ -->\r
+ <!-- module name="JavadocStyle"/ -->\r
+ \r
+ <!-- Checks for Naming Conventions. -->\r
+ <!-- See http://checkstyle.sf.net/config_naming.html -->\r
+ <module name="ConstantName"/>\r
+ <module name="LocalFinalVariableName"/>\r
+ <module name="LocalVariableName"/>\r
+ <module name="MemberName">
+ <property name="format" value="_[a-z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="MethodName" />\r
+ <module name="PackageName"/>\r
+ <module name="ParameterName">
+ <property name="format" value="^a[A-Z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="StaticVariableName">
+ <property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
+ </module>\r
+ <module name="TypeName"/>\r
+\r
+\r
+ <!-- Checks for Headers -->\r
+ <!-- See http://checkstyle.sf.net/config_header.html -->\r
+ <!-- <module name="Header"> -->\r
+ <!-- The follow property value demonstrates the ability -->\r
+ <!-- to have access to ANT properties. In this case it uses -->\r
+ <!-- the ${basedir} property to allow Checkstyle to be run -->\r
+ <!-- from any directory within a project. See property -->\r
+ <!-- expansion, -->\r
+ <!-- http://checkstyle.sf.net/config.html#properties -->\r
+ <!-- <property -->\r
+ <!-- name="headerFile" -->\r
+ <!-- value="${basedir}/java.header"/> -->\r
+ <!-- </module> -->\r
+\r
+ <!-- Following interprets the header file as regular expressions. -->\r
+ <!-- <module name="RegexpHeader"/> -->\r
+\r
+\r
+ <!-- Checks for imports -->\r
+ <!-- See http://checkstyle.sf.net/config_import.html -->\r
+ <module name="AvoidStarImport"/>\r
+ <module name="IllegalImport"/> <!-- defaults to sun.* packages -->\r
+ <module name="RedundantImport"/>\r
+ <module name="UnusedImports"/>\r
+\r
+\r
+ <!-- Checks for Size Violations. -->\r
+ <!-- See http://checkstyle.sf.net/config_sizes.html -->\r
+ <module name="FileLength"/>\r
+ <module name="LineLength">
+ <property name="max" value="120"/>
+ </module>\r
+ <module name="MethodLength"/>\r
+ <module name="ParameterNumber"/>\r
+\r
+\r
+ <!-- Checks for whitespace -->\r
+ <!-- See http://checkstyle.sf.net/config_whitespace.html -->\r
+ <module name="EmptyForIteratorPad"/>\r
+ <!-- module name="MethodParamPad"/ -->\r
+ <!-- module name="NoWhitespaceAfter"/ -->\r
+ <module name="NoWhitespaceBefore"/>\r
+ <module name="OperatorWrap"/>\r
+ <module name="ParenPad"/>\r
+ <!-- module name="TypecastParenPad"/ -->\r
+ <module name="TabCharacter"/>\r
+ <module name="WhitespaceAfter"/>\r
+ <!-- module name="WhitespaceAround">
+ <property name="tokens" value=" ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR,MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,GENERIC_START,GENERIC_END,TYPE_EXTENSION_AND,WILDCARD_TYPE"/>
+ </module -->\r
+\r
+\r
+ <!-- Modifier Checks -->\r
+ <!-- See http://checkstyle.sf.net/config_modifiers.html -->\r
+ <module name="ModifierOrder"/>\r
+ <module name="RedundantModifier"/>\r
+\r
+\r
+ <!-- Checks for blocks. You know, those {}'s -->\r
+ <!-- See http://checkstyle.sf.net/config_blocks.html -->\r
+ <module name="AvoidNestedBlocks">
+ <property name="allowInSwitchCase" value="true"/>
+ </module>\r
+ <module name="EmptyBlock"/>\r
+ <module name="LeftCurly"/>\r
+ <module name="NeedBraces"/>\r
+ <module name="RightCurly"/>\r
+\r
+\r
+ <!-- Checks for common coding problems -->\r
+ <!-- See http://checkstyle.sf.net/config_coding.html -->\r
+ <module name="AvoidInlineConditionals"/>\r
+ <!-- module name="DoubleCheckedLocking"/ --> \r
+ <module name="EmptyStatement"/>\r
+ <module name="EqualsHashCode"/>\r
+ <module name="HiddenField"/>\r
+ <module name="IllegalInstantiation"/>\r
+ <module name="InnerAssignment"/>\r
+ <module name="MagicNumber">
+ <property name="ignoreNumbers" value="-1,0,1,2,3,4"/>
+ </module>\r
+ <module name="MissingSwitchDefault"/>\r
+ <module name="RedundantThrows"/>\r
+ <module name="SimplifyBooleanExpression"/>\r
+ <module name="SimplifyBooleanReturn"/>\r
+ <!-- module name="ExplicitInitialization"/ -->\r
+\r
+ <!-- Checks for class design -->\r
+ <!-- See http://checkstyle.sf.net/config_design.html -->\r
+ <!-- module name="DesignForExtension"/ -->\r
+ <module name="FinalClass"/>\r
+ <module name="HideUtilityClassConstructor"/>\r
+ <module name="InterfaceIsType"/>\r
+ <module name="VisibilityModifier"/>\r
+\r
+\r
+ <!-- Miscellaneous other checks. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html -->\r
+ <module name="ArrayTypeStyle"/>\r
+ <!-- module name="FinalParameters"/ -->\r
+ <!-- module name="GenericIllegalRegexp">\r
+ <property name="format" value="\s+$"/>\r
+ <property name="message" value="Line has trailing spaces."/>\r
+ </module -->\r
+ <module name="TodoComment"/>\r
+ <module name="UpperEll"/>\r
+\r
+ </module>\r
+\r
+</module>\r
--- /dev/null
+<!-- PROPERTIES -->
+
+
+<!-- Default entry point for the build -->
+<target name="all" depends="clean, init, compile, jar" />
+
+<target name="startup" depends="init_directory_properties">
+ <property name="module.classpath_id" value="module.build.path" />
+ <property name="module.classpath" refid="${module.classpath_id}" />
+
+ <path id="module.build.path">
+ <fileset dir="${external.lib.dir}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+
+ <path id="module.testbuild.path">
+ <fileset dir="${test.lib.dir}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+
+</target>
+
+<target name="init" depends="import_header,startup">
+ <tstamp />
+</target>
+
+<!-- ============================================================================
+ Cleanup
+ ============================================================================ -->
+
+<target name="ant.deps">
+</target>
+
+<target name="deps" depends="import_header">
+ <antcall target="module.build.deps">
+ <param name="download.dir" value="${external.lib.dir}"/>
+ </antcall>
+ <antcall target="test.d">
+ <param name="download.dir" value="${test.lib.dir}"/>
+ </antcall>
+ <antcall target="ant.d">
+ <param name="download.dir" value="${ant.downloaded.lib.dir}"/>
+ </antcall>
+ <antcall target="module.test.deps">
+ <param name="download.dir" value="${test.lib.dir}"/>
+ </antcall>
+</target>
+
+<target name="clean" depends="init_directory_properties,base-test-clean">
+ <delete dir="${module.classes.dir}" />
+ <delete dir="${module.testclasses.dir}" />
+ <delete dir="${module.testoutput.dir}" />
+ <delete dir="${module.jars.dir}" />
+ <delete dir="${module.docbase.dir}" />
+ <delete dir="${module.report.dir}" />
+ <delete dir="${module.sql.dir}" />
+ <delete dir="${module.build.dir}"/>
+</target>
+
+<target name="clean-deps" depends="init_directory_properties">
+ <delete>
+ <fileset dir="${external.lib.dir}" includes="*"/>
+ </delete>
+ <delete>
+ <fileset dir="${test.lib.dir}" includes="*"/>
+ </delete>
+ <delete>
+ <fileset dir="${ant.downloaded.lib.dir}" includes="*"/>
+ </delete>
+</target>
+
+<!-- ============================================================================
+ Compilation of java code.
+ ============================================================================ -->
+
+<!-- Compilation of java code, requires a srcdirs variable to be
+ set to a path of one of more directories separated by : or ;
+-->
+<target name="compile-impl" depends="init">
+ <mkdir dir="${module.classes.dir}" />
+ <echo level="info" message="compile ${module.name}, source dirs ${srcdirs}" />
+ <javac srcdir="${srcdirs}" source="${javac.source}"
+ destdir="${module.classes.dir}" classpath="${module.classpath}"
+ debug="${javac.debug}" debuglevel="${javac.debug.level}" />
+ <copy todir="${module.classes.dir}">
+ <fileset dir="${module.source.dir}" excludes="**/*.java" />
+ </copy>
+</target>
+
+<!-- Compilation of regular source code -->
+<target name="compile-src" unless="ejbsource.available" depends="init">
+ <antcall target="compile-impl">
+ <param name="srcdirs" value="${module.source.dir}"/>
+ </antcall>
+</target>
+
+<!-- Compilation of regular source code and generated source code
+ for EJBs -->
+<target name="compile-src-ejb" if="ejbsource.available" depends="init">
+ <antcall target="compile-impl">
+ <param name="srcdirs" value="${module.source.dir}:${module.ejbsource.dir}"/>
+ </antcall>
+</target>
+
+<target name="compile" depends="init">
+ <available file="${module.ejbsource.dir}" type="dir" property="ejbsource.available"/>
+ <antcall target="compile-src"/>
+ <antcall target="compile-src-ejb"/>
+</target>
+
+<!-- ============================================================================
+ Generating a jar file.
+ ============================================================================ -->
+
+<target name="jar" depends="compile">
+ <mkdir dir="${module.jars.dir}" />
+ <echo level="info" message="jar cdmvbase ${module.name}" />
+ <!-- Hack: how to remove the schemas directory from the jar when it does not exist -->
+ <mkdir dir="${xmlschemas.dir}" />
+ <jar jarfile="${module.jars.dir}/${module.jar.name}"
+ compress="${jarcompress}">
+ <fileset dir="${module.classes.dir}" />
+ <fileset dir="${xmlschemas.dir}" />
+ </jar>
+</target>
+
+
+<!-- ============================================================================
+ Generate javadoc.
+ ============================================================================ -->
+
+<target name="javadoc" depends="init">
+ <!-- create javadocs -->
+ <javadoc packagenames="*" destdir="${module.javadoc.dir}"
+ author="true" version="true" use="true" private="yes"
+ windowtitle="Dragon documentation" source="${javac.source}"
+ sourcepath="${module.source.dir}"
+ classpath="${module.classpath}">
+ <arg value="-docfilessubdirs"/>
+ <arg value="-overview"/>
+ <arg value="${module.source.dir}/overview.html"/>
+ </javadoc>
+</target>
+
+<target name="testjavadoc" depends="testclasses">
+ <!-- create javadocs -->
+ <javadoc packagenames="*" destdir="${module.testjavadoc.dir}"
+ author="true" version="true" use="true" private="yes"
+ windowtitle="Dragon test documentation" source="${javac.source}"
+ sourcepath="${module.test.dir}"
+ classpathref="test.classpath">
+ </javadoc>
+</target>
+
+<!-- ============================================================================
+ Generate javadoc in pdf format
+ ============================================================================ -->
+
+<target name="pdfdoc" depends="init">
+ <!-- create javadocs -->
+ <mkdir dir="${module.pdfdoc.dir}" />
+ <javadoc packagenames="*" author="true" version="true" private="yes"
+ source="${javac.source}" doclet="com.tarsec.javadoc.pdfdoclet.PDFDoclet"
+ docletpathref="pdfdoclet.path"
+ classpath="${module.classpath}"
+ sourcepath="${module.source.dir}"
+ additionalparam="-pdf ${module.pdfdoc.dir}/${module.name}.pdf">
+ <!-- fileset dir="${module.source.dir}">
+ <include name="**/*.java" />
+ </fileset -->
+ </javadoc>
+ <echo
+ message="Unfortuantely, this target will have generated some errors but the pdf will still look fine" />
+ <echo>Result is available in ${module.pdfdoc.dir}"</echo>
+</target>
+
+<!-- ============================================================================
+ Check javadoc
+ ============================================================================ -->
+
+<target name="doccheck" depends="init">
+ <mkdir dir="${module.doccheck.dir}" />
+ <javadoc packagenames="*" destdir="${module.doccheck.dir}"
+ author="true" version="true"
+ doclet="com.sun.tools.doclets.doccheck.DocCheck"
+ docletpathref="doccheck.path"
+ classpath="${module.classpath}"
+ sourcepath="${module.source.dir}">
+ </javadoc>
+ <echo>Results are available in ${module.doccheck.dir}"</echo>
+</target>
+
+<!-- ============================================================================
+ Copy build results to a standard location.
+ ============================================================================ -->
+
+<target name="clean-dist" depends="clean">
+ <delete>
+ <fileset dir="${module.dist.dir}" includes="**"/>
+ </delete>
+</target>
+
+<target name="dist-lite-product" depends="deps,jar">
+ <mkdir dir="${module.dist.dir}"/>
+ <delete>
+ <fileset dir="${module.dist.dir}" excludes="**/CVS" />
+ </delete>
+ <copy todir="${module.dist.dir}" flatten="yes">
+ <fileset dir="${module.build.dir}">
+ <include name="**/*.jar" />
+ </fileset>
+ <fileset dir="${module.build.dir}">
+ <include name="**/*.pdf" />
+ </fileset>
+ </copy>
+ <if>
+ <isset property="webroot.dir"/>
+ <then>
+ <jar destfile="${module.dist.dir}/${module.name}.war"
+ basedir="${webroot.dir}"/>
+ </then>
+ </if>
+</target>
+
+<target name="dist-lite-test" depends="deps,testclasses">
+ <jar destfile="${module.dist.dir}/${module.testjar.name}"
+ basedir="${module.testclasses.dir}"
+ includes="**/*.class"/>
+ <jar destfile="${module.dist.dir}/${module.testresourcesjar.name}"
+ basedir="${module.testresource.dir}" />
+</target>
+
+<target name="dist-lite" depends="clean,dist-lite-product,dist-lite-test">
+ <if>
+ <isset property="post-dist-lite"/>
+ <then>
+ <antcall target="post-dist-lite"/>
+ </then>
+ </if>
+</target>
+
+<target name="dist-javadoc" depends="javadoc,pdfdoc,testjavadoc">
+ <jar destfile="${module.dist.dir}/${module.javadocjar.name}"
+ basedir="${module.javadoc.dir}" />
+ <jar destfile="${module.dist.dir}/${module.testjavadocjar.name}"
+ basedir="${module.testjavadoc.dir}" />
+ <copy todir="${module.api.forrest.dir}">
+ <fileset dir="${module.javadoc.dir}" />
+ </copy>
+</target>
+
+<target name="dist"
+ depends="dist-lite,dist-javadoc"
+ description="copying compiled sources to dist location and copy documentation to forrest site">
+ <echo
+ message="Copying build results for ${module.name} to a location where other modules can find it." />
+</target>
+
+<!-- ============================================================================
+ Check style,
+ ============================================================================ -->
+
+<target name="checkstyle-impl">
+ <checkstyle config="${build.dir}/${checkstyle.rules}"
+ failOnViolation="false">
+ <classpath>
+ <pathelement path="${checkstyle.classpath}"/>
+ </classpath>
+ <fileset dir="${checkstyle.srcdir}" includes="**/*.java"/>
+ <formatter type="xml" tofile="${module.checkstyle.dir}/${checkstyle.report}.xml"/>
+ </checkstyle>
+ <style style="${build.dir}/checkstyle-simple.xsl"
+ in="${module.checkstyle.dir}/${checkstyle.report}.xml"
+ out="${module.checkstyle.dir}/${checkstyle.report}.html"/>
+ <echo>Results are available at ${module.checkstyle.dir}/${checkstyle.report}.html</echo>
+</target>
+
+<target name="checkstyle" depends="testclasses">
+ <mkdir dir="${module.checkstyle.dir}"/>
+ <antcall target="checkstyle-impl">
+ <param name="checkstyle.classpath" refid="module.build.path"/>
+ <param name="checkstyle.srcdir" value="${module.source.dir}"/>
+ <param name="checkstyle.report" value="source-results"/>
+ </antcall>
+ <antcall target="checkstyle-impl">
+ <param name="checkstyle.classpath" refid="module.testbuild.path"/>
+ <param name="checkstyle.srcdir" value="${module.test.dir}"/>
+ <param name="checkstyle.report" value="test-results"/>
+ </antcall>
+</target>
+
+<!-- ============================================================================
+ Formatting source code.
+ ============================================================================ -->
+<target name="format" depends="init">
+ <jalopy loglevel="INFO" classpathref="module.testbuild.path"
+ convention="${jalopy.rules}">
+ <fileset dir="${module.source.dir}">
+ <include name="**/*.java" />
+ </fileset>
+ <fileset dir="${module.test.dir}">
+ <include name="**/*.java" />
+ </fileset>
+ </jalopy>
+</target>
+
+<!-- =========================================================================
+ Check redundancy in the code
+ ========================================================================= -->
+<target name="simian" depends="init">
+ <simian threshold="7">
+ <fileset dir="${module.source.dir}" includes="**/*.java" />
+ </simian>
+</target>
+
+
+<!-- =========================================================================
+ Check dependencies using macker.
+ ========================================================================= -->
+<property name="macker.file" value="macker.xml" />
+<target name="macker" depends="compile">
+ <available file="${macker.file}" property="macker.file.found" />
+ <fail message="Macker source file ${macker.file} was not found."
+ unless="macker.file.found" />
+ <macker>
+ <rules dir="." includes="macker.xml" />
+ <classes dir="${module.classes.dir}">
+ <include name="**/*.class" />
+ </classes>
+ <classpath>
+ <pathelement path="${module.classes.dir}" />
+ <path refid="module.build.path" />
+ </classpath>
+ </macker>
+</target>
+
+
+<!-- ===========================================================================
+ JUnit tests.
+ =========================================================================== -->
+
+<target name="testclasses" depends="compile">
+ <mkdir dir="${module.test.dir}" />
+ <mkdir dir="${module.testresource.dir}" />
+ <mkdir dir="${module.testclasses.dir}" />
+ <path id="test.classpath">
+ <pathelement path="${module.classes.dir}" />
+ <pathelement path="${module.test.dir}" />
+ <path refid="module.testbuild.path" />
+ <path refid="module.build.path" />
+ </path>
+ <javac srcdir="${module.test.dir}"
+ destdir="${module.testclasses.dir}"
+ debug="${javac.debug}" debuglevel="${javac.debug.level}" compiler="modern"
+ source="${javac.source}" failonerror="yes">
+ <classpath>
+ <path refid="test.classpath"/>
+ </classpath>
+ </javac>
+ <copy todir="${module.testclasses.dir}">
+ <fileset dir="${module.test.dir}" excludes="**/*.java" />
+ <fileset dir="${module.testresource.dir}" />
+ </copy>
+</target>
+
+<!-- test-base is a target mean to be used by other targets.
+ It expects one parameter names 'testedclasses' to indicate the directory
+ where the compiled test classes reside.
+-->
+
+<target name="base-test-clean">
+ <delete file="coverage.ec"/>
+ <delete file="coverage.em"/>
+</target>
+
+
+<target name="base-test-impl" depends="testclasses">
+ <mkdir dir="${module.report.dir}" />
+ <antcall target="emma"/>
+ <junit haltonfailure="${junit.halt.on.failure}" printsummary="on" dir="." fork="yes" showoutput="yes">
+ <batchtest todir="${module.report.dir}">
+ <fileset dir="${module.testclasses.dir}">
+ <include name="${testclassnames}" />
+ <!-- exclude inner classes -->
+ <exclude name="**/*$$*.class"/>
+ </fileset>
+ </batchtest>
+ <formatter type="xml" />
+ <classpath>
+ <path refid="xmlschemas.path" />
+ <pathelement path="${module.testclasses.dir}" />
+ <pathelement path="${testedclasses}" />
+ <pathelement path="${testlibrary}" />
+ <pathelement path="${module.classes.dir}" />
+ <pathelement path="${log4j.properties.dir}" /><!-- for log4j.properties -->
+ <!-- the test resource directory is added to the path as well to make it
+ easy to access resources from tests -->
+ <pathelement path="${module.testresource.dir}" />
+ <path refid="module.testbuild.path" />
+ <path refid="module.build.path" />
+ </classpath>
+ </junit>
+</target>
+
+<target name="base-test-allclasses" unless="test">
+ <echo>Testing all classes</echo>
+ <antcall target="base-test-impl">
+ <param name="testclassnames" value="**/*Test.class" />
+ </antcall>
+</target>
+
+<target name="base-test-singleclass" if="test">
+ <echo>Testing testcases that conform to the pattern *${test}*</echo>
+ <antcall target="base-test-impl">
+ <param name="testclassnames" value="**/*${test}*.class" />
+ </antcall>
+</target>
+
+<target name="base-test">
+ <antcall target="base-test-allclasses" />
+ <antcall target="base-test-singleclass" />
+</target>
+
+<!-- Run junit test -->
+
+<target name="test">
+ <antcall target="base-test">
+ <param name="testedclasses" value="${module.classes.dir}" />
+ </antcall>
+</target>
+
+<!-- reporting target used by other targets. This should not depend on
+ the test target to avoid unnecessary duplicate runs of the tests -->
+
+<target name="base-reports" depends="init">
+ <mkdir dir="${module.report.dir}/html" />
+ <mkdir dir="${module.report.dir}/html/unit" />
+ <junitreport todir="${module.report.dir}" tofile="${unitreport}">
+ <fileset dir="${module.report.dir}">
+ <include name="TEST-*.xml" />
+ </fileset>
+ <report todir="${module.report.dir}/html/unit" />
+ </junitreport>
+</target>
+
+<!-- Run junit tests with HTML reporting -->
+
+<target name="junit-reports" depends="test">
+ <antcall target="base-reports" />
+ <antcall target="emma-reports" />
+</target>
+
+<target name="reports">
+ <antcall target="junit-reports">
+ <param name="emma.enabled" value="true"/>
+ </antcall>
+</target>
+
+ <!-- Instrument classes using emma -->
+
+ <target name="emma" depends="init_directory_properties">
+ <emma enabled="${emma.enabled}">
+ <instr instrpath="${module.classes.dir}" mode="overwrite"/>
+ </emma>
+ </target>
+
+ <target name="emma-reports" depends="init_directory_properties" if="emma.enabled">
+ <mkdir dir="${module.emmareport.dir}"/>
+ <emma enabled="${emma.enabled}" >
+ <report sourcepath="${module.source.dir}">
+ <!-- collect all EMMA data dumps (metadata and runtime): -->
+ <infileset dir="." includes="*.em, *.ec" />
+
+ <txt outfile="${module.emmareport.dir}/report.txt"/>
+ <html outfile="${module.emmareport.dir}/index.html"/>
+ <!-- sourcepath>
+ <dirset dir="${src}" >
+ <include name="." />
+ </dirset>
+ </sourcepath -->
+ </report>
+ </emma>
+ </target>
+
+<!-- =======================================================================
+ FORREST
+ ======================================================================-->
+ <target name="forrest" description="runs Forrest">
+ <property name="forrest.home" location="${env.FORREST_HOME}" />
+ <property name="forrest.ant.home" location="${forrest.home}/tools/ant" />
+ <java classname="org.apache.tools.ant.Main" fork="true" failonerror="true" maxmemory="128M">
+ <classpath>
+ <fileset dir="${forrest.ant.home}/lib">
+ <include name="*.jar" />
+ </fileset>
+ <pathelement path="${java.home}/../lib/tools.jar" />
+ </classpath>
+ <sysproperty key="ant.home" value="${forrest.ant.home}" />
+ <sysproperty key="forrest.home" value="${forrest.home}" />
+ <sysproperty key="basedir" value="${basedir}" />
+ <sysproperty key="java.endorsed.dirs" value="${forrest.home}/lib/endorsed" />
+ <arg line="-f ${forrest.home}/main/forrest.build.xml" />
+ </java>
+ </target>
+
+<!-- ============================================================================
+ Database targets
+ ============================================================================ -->
+
+<!-- Runs a main program found in the test source tree.
+ Properties:
+ - my.test.program: class name of the program to run
+ - my.args: arguments to be passed to the program -->
+<target name="runTestProgram" depends="testclasses">
+ <java classname="${my.test.program}">
+ <arg line="${my.args}"/>
+ <classpath>
+ <path refid="xmlschemas.path" />
+ <path refid="junit.path" />
+ <pathelement path="${module.testclasses.dir}" />
+ <pathelement path="${testedclasses}" />
+ <pathelement path="${testlibrary}" />
+ <pathelement path="${module.classes.dir}" />
+ <pathelement path="${log4j.properties.dir}" /><!-- for log4j.properties -->
+ <!-- the test resource directory is added to the path as well to make it
+ easy to access resources from tests -->
+ <pathelement path="${module.testresource.dir}" />
+ <path refid="module.testbuild.path" />
+ <path refid="module.build.path" />
+ </classpath>
+ </java>
+</target>
+
+<property name="database.starter"
+ value="org.wamblee.persistence.test.DatabaseStarter"/>
+<target name="startdb">
+ <echo>After the database has started, you must populate the database in some way.
+ For instance, using the schemaupdate ant target</echo>
+ <antcall target="runTestProgram">
+ <param name="my.test.program" value="${database.starter}"/>
+ <param name="my.args" value=""/>
+ </antcall>
+</target>
+
+<property name="schemaupdater"
+ value="org.wamblee.test.HibernateUtils"/>
+<target name="schemaupdate">
+ <antcall target="runTestProgram">
+ <param name="my.test.program" value="${schemaupdater}"/>
+ <param name="my.args" value="${hibernate.filelist}"/>
+ </antcall>
+</target>
+
+<target name="schemaexport" depends="init">
+ <mkdir dir="${module.sql.dir}" />
+ <echo>Generating schema in output file ${module.sql.dir}/hibernate.sql</echo>
+ <antcall target="runTestProgram">
+ <param name="my.test.program" value="${schemaupdater}"/>
+ <param name="my.args" value="-export ${module.sql.dir}/hibernate.sql ${hibernate.filelist}"/>
+ </antcall>
+</target>
+
+<!-- INCLUDED MARKER -->
+<property name="build_trailer_included" value="true" />
+
+
--- /dev/null
+This directory contains a generic web crawler (basic directory) and several useful implementations build on top of this.
+
--- /dev/null
+This is a general library for implementing a web crawler.
+
+The crawler works by retrieving an HTML page and transforming the HTML
+(content + presentation) into content using XSLT stylesheets. Using a convention
+for links in the converted content, it becomes possible to build a generic interface on the retrieved pages for navigating through the content.
+
+A configuration file determines how a certain page must be retrieved and transformed.
+
+
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../../build/trailer.xml">
+ <!ENTITY deps SYSTEM "file:deps.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value="../.."/>
+ <property name="module.name" value="wamblee-crawler-basic" />
+
+ &header;
+ &deps;
+
+ <target name="module.build.deps"
+ depends="crawler.src.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="crawler.test.d">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+
+<target name="crawler.src.d"
+ depends="logging.d,dom4j.d,xerces.d,httpclient.d,jtidy.d,wamblee.support.d">
+</target>
+
+<target name="crawler.test.d" depends="wamblee.support.test.d">
+</target>
+
+
+<property name="crawler.dist.dir" value="${lib.dir}/wamblee/crawler/basic"/>
+<target name="wamblee.crawler.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${crawler.dist.dir}">
+ <include name="wamblee-crawler-basic.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.crawler.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${crawler.dist.dir}">
+ <include name="wamblee-crawler-basic-test.jar"/>
+ </fileset>
+ </copy>
+</target>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=DEBUG
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.w3c.dom.Document;
+import org.w3c.tidy.Tidy;
+import org.wamblee.xml.DomUtils;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * General support claas for all kinds of requests.
+ */
+public abstract class AbstractPageRequest implements PageRequest {
+
+ private static final Log LOG = LogFactory.getLog(AbstractPageRequest.class);
+
+ private static final String REDIRECT_HEADER = "Location";
+
+ private int _maxTries;
+
+ private int _maxDelay;
+
+ private NameValuePair[] _params;
+
+ private NameValuePair[] _headers;
+
+ private String _xslt;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the request.
+ *
+ * @param aMaxTries
+ * Maximum retries to perform.
+ * @param aMaxDelay
+ * Maximum delay before executing a request.
+ * @param aParams
+ * Request parameters to use.
+ * @param aHeaders
+ * Request headers to use.
+ * @param aXslt
+ * XSLT used to convert the response.
+ */
+ protected AbstractPageRequest(int aMaxTries, int aMaxDelay,
+ NameValuePair[] aParams, NameValuePair[] aHeaders, String aXslt, XslTransformer aTransformer) {
+ if (aParams == null) {
+ throw new IllegalArgumentException("aParams is null");
+ }
+ if (aHeaders == null) {
+ throw new IllegalArgumentException("aHeaders is null");
+ }
+ if (aXslt == null) {
+ throw new IllegalArgumentException("aXslt is null");
+ }
+ _maxTries = aMaxTries;
+ _maxDelay = aMaxDelay;
+ _params = aParams;
+ _headers = aHeaders;
+ _xslt = aXslt;
+ _transformer = aTransformer;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#overrideXslt(java.lang.String)
+ */
+ public void overrideXslt(String aXslt) {
+ _xslt = aXslt;
+ }
+
+ /**
+ * Gets the parameters for the request.
+ *
+ * @param aParams Additional parameters to use, obtained from another page, most likely as
+ * hidden form fields.
+ * @return Request parameters.
+ */
+ protected NameValuePair[] getParameters(NameValuePair[] aParams) {
+ List<NameValuePair> params = new ArrayList<NameValuePair>();
+ params.addAll(Arrays.asList(_params));
+ params.addAll(Arrays.asList(aParams));
+ return params.toArray(new NameValuePair[0]);
+ }
+
+ /**
+ * Gets the headers for the request.
+ * @return Request headers.
+ */
+ protected NameValuePair[] getHeaders() {
+ return _headers;
+ }
+
+ /**
+ * Executes the request with a random delay and with a maximum number of
+ * retries.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method representing the request.
+ * @return XML document describing the response.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case transformation of the HTML to XML fails.
+ */
+ protected Document executeMethod(HttpClient aClient, HttpMethod aMethod)
+ throws IOException, TransformerException {
+
+ for (NameValuePair header: getHeaders()) {
+ aMethod.setRequestHeader(header.getName(), header.getValue());
+ }
+
+ int triesLeft = _maxTries;
+ while (triesLeft > 0) {
+ triesLeft--;
+ try {
+ return executeMethodWithoutRetries(aClient, aMethod);
+ } catch (TransformerException e) {
+ if (triesLeft == 0) {
+ throw e;
+ }
+ }
+ }
+ throw new RuntimeException("Code should never reach this point");
+ }
+
+ /**
+ * Executes the request without doing any retries in case XSLT
+ * transformation fails.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method to execute.
+ * @return XML document containing the result.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case transformation of the result to XML fails.
+ */
+ protected Document executeMethodWithoutRetries(HttpClient aClient,
+ HttpMethod aMethod) throws IOException, TransformerException {
+ try {
+ aMethod = executeWithRedirects(aClient, aMethod);
+ byte[] xhtmlData = getXhtml(aMethod);
+
+
+ Document transformed = _transformer.transform(xhtmlData,
+ _transformer.resolve(_xslt));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Transformer transformer = TransformerFactory.newInstance()
+ .newTransformer();
+ transformer.setParameter(OutputKeys.INDENT, "yes");
+ transformer.setParameter(OutputKeys.METHOD, "xml");
+ transformer.transform(new DOMSource(transformed), new StreamResult(
+ os));
+ LOG.debug("Transformed result is \n" + os.toString());
+ return transformed;
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException("Transformer configuration problem", e);
+ } finally {
+ // Release the connection.
+ aMethod.releaseConnection();
+ }
+ }
+
+ /**
+ * Gets the result of the HTTP method as an XHTML document.
+ *
+ * @param aMethod
+ * Method to invoke.
+ * @return XHTML as a byte array.
+ * @throws IOException
+ * In case of problems obtaining the XHTML.
+ */
+ private byte[] getXhtml(HttpMethod aMethod) throws IOException {
+ // Transform the HTML into wellformed XML.
+ Tidy tidy = new Tidy();
+ tidy.setXHTML(true);
+ tidy.setQuiet(true);
+ tidy.setShowWarnings(false);
+
+ // We write the jtidy output to XML since the DOM tree it produces is
+ // not namespace aware and namespace awareness is required by XSLT.
+ // An alternative is to configure namespace awareness of the XML parser
+ // in a system wide way.
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Document w3cDoc = tidy.parseDOM(aMethod.getResponseBodyAsStream(), os);
+ DomUtils.removeDuplicateAttributes(w3cDoc);
+ LOG.debug("Content of response is \n" + os.toString());
+
+ ByteArrayOutputStream xhtml = new ByteArrayOutputStream();
+ XMLSerializer serializer = new XMLSerializer(xhtml, new OutputFormat());
+ serializer.serialize(w3cDoc);
+ xhtml.flush();
+
+ return xhtml.toByteArray();
+ }
+
+ /**
+ * Sleeps for a random time but no more than the maximum delay.
+ *
+ */
+ private void delay() {
+ try {
+ Thread.sleep((long) ((float) _maxDelay * Math.random()));
+ } catch (InterruptedException e) {
+ return; // to satisfy checkstyle
+ }
+ }
+
+ /**
+ * Executes the request and follows redirects if needed.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method to use.
+ * @return Final HTTP method used (differs from the parameter passed in in
+ * case of redirection).
+ * @throws IOException
+ * In case of network problems.
+ */
+ private HttpMethod executeWithRedirects(HttpClient aClient,
+ HttpMethod aMethod) throws IOException {
+ delay();
+ int statusCode = aClient.executeMethod(aMethod);
+
+ switch (statusCode) {
+ case HttpStatus.SC_OK: {
+ return aMethod;
+ }
+ case HttpStatus.SC_MOVED_PERMANENTLY:
+ case HttpStatus.SC_MOVED_TEMPORARILY:
+ case HttpStatus.SC_SEE_OTHER: {
+ aMethod.releaseConnection();
+ Header header = aMethod.getResponseHeader(REDIRECT_HEADER);
+ aMethod = new GetMethod(header.getValue());
+ return executeWithRedirects(aClient, aMethod); // TODO protect
+ // against infinite
+ // recursion.
+ }
+ default: {
+ throw new IOException("Method failed: "
+ + aMethod.getStatusLine());
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.dom4j.Element;
+
+/**
+ * An action defined on a page.
+ */
+public interface Action {
+
+ /**
+ * The name of the action.
+ * @return Action name.
+ */
+ String getName();
+
+ /**
+ * Executes the action.
+ * @return New page as a result of the action.
+ * @throws PageException In case of an error obtaining the page.
+ */
+ Page execute() throws PageException;
+
+ /**
+ * Gets a description of the action. THe element returned is the action element
+ * itself.
+ * @return Content as XML.
+ */
+ Element getContent();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Configuration which determines how a specific page must be retrieved and
+ * what transformation should be applied to it.
+ */
+public interface Configuration {
+
+ /**
+ * Gets the page request based on the URL.
+ * @param aUrl Url of the page to retrieve.
+ * @return Page request.
+ */
+ PageRequest getRequest(String aUrl);
+
+ /**
+ * Gets the page request based on the type of the page instead
+ * of on the URL.
+ * @param aType Type of page.
+ * @return Page request.
+ */
+ PageRequest getRequest(PageType aType);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.apache.commons.httpclient.NameValuePair;
+
+
+/**
+ * The object that actually obtains pages based on URL.
+ */
+public interface Crawler {
+
+ /**
+ * Gets the content for a specific page.
+ * @param aUrl Url of page.
+ * @param aParameters Paremeters to supply.
+ * @return Page to retrieve.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Page getPage(String aUrl, NameValuePair[] aParameters) throws PageException;
+
+ /**
+ * Gets the content for a specific page.
+ * @param aUrl Url of page.
+ * @param aParameters Parameters to supply.
+ * @param aType Type of page.
+ * @return Page.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Page getPage(String aUrl, NameValuePair[] aParameters, PageType aType) throws PageException;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.w3c.dom.Document;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Gets a page by issueing a get request.
+ */
+public class GetPageRequest extends AbstractPageRequest {
+
+ /**
+ * Constructs the request.
+ * @param aMaxTries Maximum number of retries.
+ * @param aMaxDelay Maximum delay before executing the request.
+ * @param aParams Request parameters to use.
+ * @param aHeaders Request headers to use.
+ * @param aXslt XSLT to use.
+ */
+ public GetPageRequest(int aMaxTries, int aMaxDelay, NameValuePair[] aParams,
+ NameValuePair[] aHeaders, String aXslt, XslTransformer aTransformer) {
+ super(aMaxTries, aMaxDelay, aParams, aHeaders, aXslt, aTransformer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#getPage(org.apache.commons.httpclient.HttpClient)
+ */
+ public Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient)
+ throws PageException {
+ HttpMethod method = new GetMethod(aUrl);
+ NameValuePair[] params = getParameters(aParams);
+ if (params.length > 0) {
+ String oldQueryString = method.getQueryString();
+ method.setQueryString(params);
+ String queryString = method.getQueryString();
+ if (oldQueryString.length() > 0) {
+ queryString = queryString + '&' + oldQueryString;
+ method.setQueryString(queryString);
+ }
+ }
+ try {
+ return executeMethod(aClient, method);
+ } catch (TransformerException e) {
+ throw new PageException("Transformation problem for url " + aUrl, e);
+ } catch (IOException e) {
+ throw new PageException("Problem getting " + aUrl, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.dom4j.Element;
+
+/**
+ * Represents a retrieved page.
+ */
+public interface Page {
+
+ /**
+ * Gets the content of the page as raw XML.
+ * @return Page content.
+ */
+ Element getContent();
+
+ /**
+ * Obtains the links available on the page.
+ * @return Link names.
+ */
+ Action[] getActions();
+
+ /**
+ * Gets the named action. Only works if the action name is unique.
+ * @param aName Name of the action.
+ * @return Action object.
+ */
+ Action getAction(String aName);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Exception thrown when there is a problem in retrieving or transforming the
+ * page.
+ */
+public class PageException extends Exception {
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public PageException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aCause Cause of the exception.
+ */
+ public PageException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.w3c.dom.Document;
+
+/**
+ * Represents a specific request to obtain and transform a page.
+ */
+public interface PageRequest {
+
+ /**
+ * Gets a page as an XML document.
+ * @param aUrl Url of the page.
+ * @param aParams Additional parameters to supply.
+ * @param aClient Http client to use.
+ * @return Client.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient) throws PageException;
+
+ /**
+ * Overrides the Xslt to use. This is used when the transformed page specifies
+ * the page type explicitly for an action.
+ * @param aXslt Xslt to use.
+ */
+ void overrideXslt(String aXslt);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Represents the type of a page determining how the HTML should be transformed into
+ * XML.
+ */
+public class PageType {
+
+ /**
+ * Type string.
+ */
+ private String _type;
+
+ /**
+ * Constructs the type.
+ * @param aType Type.
+ */
+ public PageType(String aType) {
+ _type = aType;
+ }
+
+ /**
+ * Gets the type.
+ * @return Type.
+ */
+ public String getType() {
+ return _type;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "PageType(type='" + _type + "')";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PageType)) {
+ return false;
+ }
+ return toString().equals(obj.toString());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.w3c.dom.Document;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Retrieving pages using the post method.
+ */
+public class PostPageRequest extends AbstractPageRequest {
+
+ /**
+ * Constructs the request.
+ * @param aMaxTries Maximum number of retries.
+ * @param aMaxDelay Maximum delay before executing the request.
+ * @param aParams Request parameters to use.
+ * @param aHeaders Request headers to use.
+ * @param aXslt XSLT to use.
+ */
+ public PostPageRequest(int aMaxTries, int aMaxDelay,
+ NameValuePair[] aParams,
+ NameValuePair[] aHeaders,
+ String aXslt, XslTransformer aTransformer) {
+ super(aMaxTries, aMaxDelay, aParams, aHeaders, aXslt, aTransformer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#execute(java.lang.String,
+ * org.apache.commons.httpclient.HttpClient)
+ */
+ public Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient)
+ throws PageException {
+ PostMethod method = new PostMethod(aUrl);
+ method.addParameters(getParameters(aParams));
+ try {
+ return executeMethod(aClient, method);
+ } catch (TransformerException e) {
+ throw new PageException("Transformation problem for url " + aUrl, e);
+ } catch (IOException e) {
+ throw new PageException("Problem getting page " + aUrl, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Action implementation.
+ */
+public class ActionImpl implements Action {
+
+ private Crawler _crawler;
+
+ private Element _content;
+
+ private String _name;
+
+ private String _reference;
+
+ private PageType _type;
+
+ private NameValuePair[] _parameters;
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCrawler
+ * Crawler to use.
+ * @param aContent
+ * Content of the action element in the page where the action
+ * occurs.
+ * @param aName
+ * Name of the action.
+ * @param aReference
+ * URL of the reference.
+ * @param aParameters Parameters to use for the action.
+ */
+ public ActionImpl(Crawler aCrawler, Element aContent, String aName,
+ String aReference, NameValuePair[] aParameters) {
+ _crawler = aCrawler;
+ _content = aContent;
+ _name = aName;
+ _reference = aReference;
+ _type = null;
+ _parameters = aParameters;
+ }
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCrawler
+ * Crawler to use.
+ * @param aContent
+ * Content of the action element in the page where the action
+ * occurs.
+ * @param aName
+ * Name of the action.
+ * @param aReference
+ * URL of the reference.
+ * @param aType
+ * Type of the referenced page.
+ * @param aParameters Parameters to use.
+ */
+ public ActionImpl(Crawler aCrawler, Element aContent, String aName,
+ String aReference, PageType aType, NameValuePair[] aParameters) {
+ _crawler = aCrawler;
+ _content = aContent;
+ _name = aName;
+ _reference = aReference;
+ _type = aType;
+ _parameters = aParameters;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#getName()
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#execute()
+ */
+ public Page execute() throws PageException {
+ if (_type == null) {
+ return _crawler.getPage(_reference, _parameters);
+ }
+ return _crawler.getPage(_reference, _parameters, _type);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#getContent()
+ */
+ public Element getContent() {
+ return _content;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if ( !(obj instanceof ActionImpl )) {
+ return false;
+ }
+ ActionImpl action = (ActionImpl)obj;
+ return _reference.equals(action._reference) &&
+ _type.equals(action._type);
+ }
+}
--- /dev/null
+package org.wamblee.crawler.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.xml.XslTransformer;
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.
+ */
+
+/**
+ * Test application which uses the crawler.
+ */
+public final class App {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private App() {
+ // Empty
+ }
+
+ /**
+ * Runs a test program.
+ *
+ * @param aArgs
+ * Arguments. First argument is the crawler config file name and
+ * second argument is the start url.
+ * @throws Exception
+ * In case of problems.
+ */
+ public static void main(String[] aArgs) throws Exception {
+ String configFileName = aArgs[0];
+ String starturl = aArgs[1];
+
+ ConfigurationParser parser = new ConfigurationParser(new XslTransformer());
+ InputStream configFile = new FileInputStream(new File(configFileName));
+ Configuration config = parser.parse(configFile);
+
+ HttpClient client = new HttpClient();
+ // client.getHostConfiguration().setProxy("localhost", 3128);
+
+ Crawler crawler = new CrawlerImpl(client, config);
+
+ System.out.println("Retrieving: " + starturl);
+ Page page = crawler.getPage(starturl, new NameValuePair[0]);
+ showPage(page);
+ page = page.getAction("channels-favorites").execute();
+ recordInterestingShows(page);
+ showPage(page);
+ page = page.getAction("Nederland 1").execute();
+ showPage(page);
+ page = page.getAction("right-now").execute();
+ showPage(page);
+ page = page.getAction("Het elfde uur").execute();
+ showPage(page);
+ }
+
+ /**
+ * @param starturl
+ * @param crawler
+ */
+ private static void showPage(Page aPage) {
+ Action[] links = aPage.getActions();
+ for (Action link : links) {
+ System.out.println("Link found '" + link.getName() + "'");
+ }
+ Element element = aPage.getContent();
+ System.out.println("Retrieved content: " + element.asXML());
+ }
+
+ private static void recordInterestingShows(Page page) throws PageException {
+ Action[] channels = page.getActions();
+ for (Action channel : channels) {
+ examineChannel(channel.getName(), channel.execute().getAction(
+ "right-now").execute());
+ }
+ }
+
+ private static void examineChannel(String aChannel, Page aPage)
+ throws PageException {
+ Action[] programs = aPage.getActions();
+ for (Action program : programs) {
+ System.out.println(aChannel + " - " + program.getName());
+ if (program.getName().toLowerCase().matches(".*babe.*")) {
+ Page programPage = program.execute();
+ Action record = programPage.getAction("record");
+ System.out.println("Recording possible: " + record != null);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.util.regex.Pattern;
+
+/**
+ * Configuration item for obtaining an object in case a pattern matches.
+ */
+class ConfigItem<ValueType> {
+
+ private Pattern _pattern;
+
+ private ValueType _value;
+
+ /**
+ * Constructs the item.
+ * @param aPattern Pattern.
+ * @param aValue Value.
+ */
+ protected ConfigItem(String aPattern, ValueType aValue) {
+ _pattern = Pattern.compile(aPattern);
+ _value = aValue;
+ }
+
+ /**
+ * Returns the object in case the value matches.
+ * @param aValue Value to match.
+ * @return Object in case there is a match, null otherwise.
+ */
+ protected ValueType match(String aValue) {
+ if (!_pattern.matcher(aValue).matches()) {
+ return null;
+ }
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.util.List;
+
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Implementation of the configuration for the crawler.
+ */
+public class ConfigurationImpl implements Configuration {
+
+ private List<UrlConfig> _urlConfig;
+
+ private List<PageTypeConfig> _pageTypeConfig;
+
+ /**
+ * Constructs the configuration.
+ * @param aUrlConfig List of URL configuration elements.
+ * @param aPageTypeConfig List of page type configuration elements.
+ */
+ public ConfigurationImpl(List<UrlConfig> aUrlConfig,
+ List<PageTypeConfig> aPageTypeConfig) {
+ _urlConfig = aUrlConfig;
+ _pageTypeConfig = aPageTypeConfig;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Configuration#getRequest(java.lang.String)
+ */
+ public PageRequest getRequest(String aUrl) {
+
+ for (UrlConfig config : _urlConfig) {
+ PageRequest request = config.getRequest(aUrl);
+ if (request != null) {
+ return request;
+ }
+ }
+ throw new RuntimeException("No configuration matched the URL '" + aUrl
+ + "'");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Configuration#getRequest(org.wamblee.crawler.PageType)
+ */
+ public PageRequest getRequest(PageType aType) {
+ for (PageTypeConfig config : _pageTypeConfig) {
+ PageRequest request = config.getRequest(aType.getType());
+ if (request != null) {
+ return request;
+ }
+ }
+ throw new RuntimeException("No configuration matched type '" + aType
+ + "'");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.GetPageRequest;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PostPageRequest;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Parsing of the configuration from an XML file.
+ */
+public class ConfigurationParser {
+
+ private static final String ELEM_URL = "url";
+
+ private static final String ELEM_TYPE = "type";
+
+ private static final String ELEM_PATTERN = "pattern";
+
+ private static final String ELEM_METHOD = "method";
+
+ private static final String ELEM_XSLT = "xslt";
+
+ private static final String ELEM_PARAM = "param";
+
+ private static final String ELEM_HEADER = "header";
+
+ private static final String AT_NAME = "name";
+
+ private static final String AT_VALUE = "value";
+
+ private static final String METHOD_POST = "post";
+
+ private static final String METHOD_GET = "get";
+
+ private static final int MAX_TRIES = 3;
+
+ private static final int MAX_DELAY = 10000;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the configuration parser.
+ */
+ public ConfigurationParser(XslTransformer aTransformer) {
+ _transformer = aTransformer;
+ }
+
+ /**
+ * Parses the configuration from an input stream.
+ * @param aStream Input file.
+ * @return Configuration.
+ */
+ public Configuration parse(InputStream aStream) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(aStream);
+
+ Element root = document.getRootElement();
+ List<UrlConfig> urlConfigs = parseUrlConfigs(root);
+ List<PageTypeConfig> pageTypeConfigs = parsePageTypeConfigs(root);
+ return new ConfigurationImpl(urlConfigs, pageTypeConfigs);
+ } catch (DocumentException e) {
+ throw new RuntimeException("Problem parsing config file", e);
+ }
+ }
+
+ /**
+ * Parses the URL-based configuration.
+ * @param aRoot Root of the configuration file document.
+ * @return List of URL-based configurations.
+ */
+ private List<UrlConfig> parseUrlConfigs(Element aRoot) {
+ List<UrlConfig> configs = new ArrayList<UrlConfig>();
+ for (Iterator i = aRoot.elementIterator(ELEM_URL); i.hasNext();) {
+ Element url = (Element) i.next();
+ UrlConfig config = parseUrlConfig(url);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ /**
+ * Parses the page type based configurations.
+ * @param aRoot Root of the configuration file document.
+ * @return LIst of page type based configurations.
+ */
+ private List<PageTypeConfig> parsePageTypeConfigs(Element aRoot) {
+ List<PageTypeConfig> configs = new ArrayList<PageTypeConfig>();
+ for (Iterator i = aRoot.elementIterator(ELEM_TYPE); i.hasNext();) {
+ Element url = (Element) i.next();
+ PageTypeConfig config = parsePageTypeConfig(url);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ /**
+ * Parses a URL-based configuration.
+ * @param aUrlElem Configuration element.
+ * @return Configuration.
+ */
+ private UrlConfig parseUrlConfig(Element aUrlElem) {
+ String pattern = aUrlElem.elementText(ELEM_PATTERN);
+ PageRequest request = parseRequestConfig(aUrlElem);
+ return new UrlConfig(pattern, request);
+ }
+
+ /**
+ * Parses a page type based configuration.
+ * @param aTypeElem Configuration element.
+ * @return Configuration.
+ */
+ private PageTypeConfig parsePageTypeConfig(Element aTypeElem) {
+ String pattern = aTypeElem.elementText(ELEM_PATTERN);
+ PageRequest request = parseRequestConfig(aTypeElem);
+ return new PageTypeConfig(pattern, request);
+ }
+
+ /**
+ * Parses a request configuration describing how to execute requests.
+ * @param aElem Configuration element.
+ * @return Page request.
+ */
+ private PageRequest parseRequestConfig(Element aElem) {
+ String method = aElem.elementText(ELEM_METHOD);
+ String xslt = aElem.elementText(ELEM_XSLT);
+ List<NameValuePair> params = parseNameValuePairs(aElem, ELEM_PARAM);
+ List<NameValuePair> headers = parseNameValuePairs(aElem, ELEM_HEADER);
+
+ NameValuePair[] paramsArray = params.toArray(new NameValuePair[0]);
+ NameValuePair[] headersArray = headers.toArray(new NameValuePair[0]);
+ PageRequest request;
+ if (METHOD_POST.equals(method)) {
+ request = new PostPageRequest(MAX_TRIES, MAX_DELAY, paramsArray, headersArray,
+ xslt, _transformer);
+ } else if (METHOD_GET.equals(method) || method == null) {
+ request = new GetPageRequest(MAX_TRIES, MAX_DELAY, paramsArray, headersArray,
+ xslt, _transformer);
+ } else {
+ throw new RuntimeException("Unknown request method '" + method
+ + "'. Only " + METHOD_GET + " and " + METHOD_POST
+ + " are supported");
+ }
+ return request;
+ }
+
+ /**
+ * @param aElem
+ * @return
+ */
+ private List<NameValuePair> parseNameValuePairs(Element aElem, String aElemName) {
+ List<NameValuePair> headers = new ArrayList<NameValuePair>();
+ for (Iterator i = aElem.elementIterator(aElemName); i.hasNext();) {
+ Element paramElem = (Element) i.next();
+ NameValuePair header = parseParameter(paramElem);
+ headers.add(header);
+ }
+ return headers;
+ }
+
+ /**
+ * Parses a parameter definition.
+ * @param aParam Parameter.
+ * @return Name value pair describing a parameter.
+ */
+ private NameValuePair parseParameter(Element aParam) {
+ String name = aParam.attributeValue(AT_NAME);
+ String value = aParam.attributeValue(AT_VALUE);
+ return new NameValuePair(name, value);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+import org.dom4j.io.DOMReader;
+import org.w3c.dom.Document;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Crawler implementation.
+ */
+public class CrawlerImpl implements Crawler {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerImpl.class);
+
+ private HttpClient _client;
+
+ private Configuration _config;
+
+ /**
+ * Constructs the crawler.
+ *
+ * @param aClient
+ * Http client to use.
+ * @param aConfig
+ * Configuration.
+ */
+ public CrawlerImpl(HttpClient aClient, Configuration aConfig) {
+ _client = aClient;
+ _config = aConfig;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Crawler#getPage(java.lang.String)
+ */
+ public Page getPage(String aUrl, NameValuePair[] aParams) throws PageException {
+ LOG.debug("Getting page: url = '" + aUrl + "'");
+ PageRequest request = _config.getRequest(aUrl);
+ Document content = request.execute(aUrl, aParams, _client);
+ return transformToDom4jDoc(aUrl, content);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Crawler#getPage(java.lang.String,
+ * java.lang.String)
+ */
+ public Page getPage(String aUrl, NameValuePair[] aParams, PageType aType) throws PageException {
+ LOG.debug("Getting page: url = '" + aUrl + "', type = '" + aType + "'");
+ PageRequest request = _config.getRequest(aType);
+ Document content = request.execute(aUrl, aParams, _client);
+ return transformToDom4jDoc(aUrl, content);
+ }
+
+ /**
+ * Converts a w3c DOM document to a page object.
+ * @param content DOM document.
+ * @return
+ */
+ private Page transformToDom4jDoc(String aUrl, Document content) {
+ DOMReader reader = new DOMReader();
+ org.dom4j.Document dom4jDoc = reader.read(content);
+ Element root = dom4jDoc.getRootElement();
+ dom4jDoc.remove(root);
+
+ return new PageImpl(aUrl, this, replaceReferencesWithContent(root));
+ }
+
+ /**
+ * Perform crawling. Find references in the retrieved content and replace
+ * them by the content they refer to by retrieving the appropriate pages as
+ * well.
+ *
+ * @param content
+ * Content which must be made complete.
+ * @return Fully processed content.
+ */
+ private Element replaceReferencesWithContent(Element content) {
+ return content; // TODO implement.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.XPath;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Page implementation.
+ */
+public class PageImpl implements Page {
+
+ private static final String ELEM_NAME = "action";
+
+ private static final String ATT_NAME = "name";
+
+ private static final String ATT_HREF = "reference";
+
+ private static final String ATT_TYPE = "type";
+
+ private static final String ELEM_PARAM = "param";
+
+ private static final String ATT_VALUE = "value";
+
+ private String _href;
+
+ private Crawler _crawler;
+
+ private Element _content;
+
+ private Action[] _actions;
+
+ /**
+ * Constructs a page.
+ *
+ * @param aContent
+ */
+ public PageImpl(String aHref, Crawler aCrawler, Element aContent) {
+ _href = aHref;
+ _crawler = aCrawler;
+ _content = aContent;
+ _actions = computeActions();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getLinkNames()
+ */
+ private Action[] computeActions() {
+ XPath xpath = DocumentHelper.createXPath(ELEM_NAME);
+ List<Element> results = (List<Element>) xpath.selectNodes(_content);
+ List<Action> names = new ArrayList<Action>();
+ for (Element elem : results) {
+ String name = elem.attributeValue(ATT_NAME);
+ String href = elem.attributeValue(ATT_HREF);
+ String type = elem.attributeValue(ATT_TYPE);
+ NameValuePair[] params = getMandatoryParameters(elem);
+ href = absolutizeHref(_href, href);
+ if (type == null) {
+ names.add(new ActionImpl(_crawler, elem, name, href, params));
+ } else {
+ names.add(new ActionImpl(_crawler, elem, name, href,
+ new PageType(type), params));
+ }
+ }
+ return names.toArray(new Action[0]);
+ }
+
+ /**
+ * Absolutize the hyperlink
+ * @param aPageHref Absolute page reference.
+ * @param aLinkHref Possibly relative link reference.
+ * @return Absolute hyperlink.
+ */
+ private String absolutizeHref(String aPageHref, String aLinkHref) {
+
+ try {
+ URL pageUrl = new URL(aPageHref);
+ URL newUrl = new URL(pageUrl, aLinkHref);
+ return newUrl.toString(); // TODO need to use URL instead of String throughout the code.
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Malformed URL", e);
+ }
+ }
+
+ private NameValuePair[] getMandatoryParameters(Element aAction) {
+ List<NameValuePair> result = new ArrayList<NameValuePair>();
+ for (Element param: (List<Element>)aAction.elements(ELEM_PARAM)) {
+ String name = param.attributeValue(ATT_NAME);
+ String value = param.attributeValue(ATT_VALUE);
+ result.add(new NameValuePair(name, value));
+ }
+ return result.toArray(new NameValuePair[0]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getContent()
+ */
+ public Element getContent() {
+ return _content;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getActions()
+ */
+ public Action[] getActions() {
+ return _actions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getAction(java.lang.String)
+ */
+ public Action getAction(String aName) {
+ List<Action> results = new ArrayList<Action>();
+ for (Action action : _actions) {
+ if (action.getName().equals(aName)) {
+ results.add(action);
+ }
+ }
+ if (results.size() == 0) {
+ return null;
+ }
+ if (results.size() > 1) {
+ throw new RuntimeException("Duplicate action '" + aName + "'");
+ }
+ return results.get(0);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.wamblee.crawler.PageRequest;
+
+/**
+ * Page type configuration.
+ */
+public class PageTypeConfig extends ConfigItem<PageRequest> {
+
+ /**
+ * Constructs the configuration.
+ * @param aPattern Page type pattern.
+ * @param aRequest Page request.
+ */
+ public PageTypeConfig(String aPattern, PageRequest aRequest) {
+ super(aPattern, aRequest);
+ }
+
+ /**
+ * Returns the request in case the type matches.
+ * @param aType Page type.
+ * @return Request if the type matches, null otherwise.
+ */
+ public PageRequest getRequest(String aType) {
+ return match(aType);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.wamblee.crawler.PageRequest;
+
+/**
+ * Represents the configuration for specific URLs.
+ */
+public class UrlConfig extends ConfigItem<PageRequest> {
+ /**
+ * Constructs the information for how to perform a request for a specific
+ * URL.
+ *
+ * @param aPattern
+ * Pattern that the URL must match.
+ * @param aRequest
+ * Request that must be executed to retrieve the URL.
+ */
+ public UrlConfig(String aPattern, PageRequest aRequest) {
+ super(aPattern, aRequest);
+ }
+
+ /**
+ * Gets the request to execute.
+ *
+ * @return Request, or null if the URL does not match.
+ */
+ public PageRequest getRequest(String aUrl) {
+ return match(aUrl);
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides the implementations of the web crawler interfaces.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides the basic interfaces for a web crawler.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY delegator SYSTEM "file:../build/delegator.xml">
+]>
+
+<project name="utils" basedir=".">
+
+ <property name="project.home" value=".."/>
+
+ &header;
+
+ <property name="projects" value="basic,kiss,kissweb"/>
+
+ &delegator;
+
+</project>
--- /dev/null
+This is a crawler for the KiSS Electronic Program Guide that can be used for instance with the KiSS DP558 hard-disc recorder. It uses the basic crawler for its implementation.
+
+Based on preferences for recording programs, the crawler automatically records programs that are scheduled to run on the same day. This saves a lot of manual work in recording programs.
+
+The final idea is to define ones own interests in television programs and have the crawler record them automatically or send notifications of possibly interesting programs. Whether programs should be recorded can be determined by several criteria such as program title, channel, time of day, and keywords in the description.
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../../build/trailer.xml">
+ <!ENTITY crawlerdeps SYSTEM "file:../basic/deps.xml">
+ <!ENTITY deps SYSTEM "file:deps.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value="../.."/>
+ <property name="module.name" value="wamblee-crawler-kiss" />
+
+ &header;
+ &crawlerdeps;
+ &deps;
+
+ <target name="module.build.deps"
+ depends="kisscrawler.src.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="kisscrawler.test.d">
+ </target>
+
+ <property name="post-dist-lite" value="yes"/>
+
+ <target name="post-dist-lite" depends="init_directory_properties">
+ <property name="module.install.dir" value="${module.build.dir}/kiss-crawler"/>
+
+ <mkdir dir="${module.install.dir}"/>
+ <mkdir dir="${module.install.dir}/bin"/>
+ <copy todir="${module.install.dir}/bin">
+ <fileset dir="conf/kiss">
+ <include name="run.*"/>
+ </fileset>
+ </copy>
+
+ <mkdir dir="${module.install.dir}/conf"/>
+ <copy todir="${module.install.dir}/conf">
+ <fileset dir="conf/kiss">
+ <include name="config.xml.example"/>
+ <include name="programs.xml"/>
+ <include name="org.wamblee.crawler.properties"/>
+ </fileset>
+ </copy>
+
+ <mkdir dir="${module.install.dir}/lib"/>
+ <copy todir="${module.install.dir}/lib" flatten="yes">
+ <fileset dir="${external.lib.dir}" includes="*.jar"/>
+ <fileset dir="${module.build.dir}" includes="**/*.jar"/>
+ </copy>
+
+ <zip destfile="${module.dist.dir}/kiss-crawler-bin.zip"
+ basedir="${module.install.dir}"/>
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<crawler>
+
+ <type>
+ <pattern>login</pattern>
+ <method>post</method>
+ <xslt>login-graphic.xsl</xslt>
+ <param name="user" value="youruserid"/>
+ <param name="passwd" value="yourpassword"/>
+ <param name="GMode" value="GraphicMode"/>
+ <param name="SavePlayerID" value="1"/>
+ <param name="submit" value="Login"/>
+ <header name="Referer" value="http://epg.kml.kiss-technology.com/login.php"/>
+ </type>
+
+ <type>
+ <pattern>channels-favorites</pattern>
+ <xslt>channels-favorites-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>channel-overview</pattern>
+ <xslt>channel-overview.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>right-now</pattern>
+ <xslt>channel-right-now-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>tomorrow</pattern>
+ <xslt>channel-right-now-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>program-info</pattern>
+ <xslt>program-info-mobile.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>recorded</pattern>
+ <xslt>recorded.xsl</xslt>
+ </type>
+
+ <url>
+ <pattern>http://epg.kml.kiss-technology.com/login.php</pattern>
+ <xslt>mainpage.xsl</xslt>
+ </url>
+
+ <url>
+ <pattern>.*</pattern>
+ <method>get</method>
+ <xslt>identity.xsl</xslt>
+ </url>
+</crawler>
+
--- /dev/null
+
+
+############################################################################
+# Mail server configuration
+############################################################################
+org.wamblee.crawler.smtp.host=falcon
+org.wamblee.crawler.smtp.port=25
+org.wamblee.crawler.smtp.username=
+org.wamblee.crawler.smtp.password=
+
+############################################################################
+# Mail notification configuration
+############################################################################
+org.wamblee.crawler.notification.from=kiss@wamblee.org
+org.wamblee.crawler.notification.to=erik@bladibla.org
+org.wamblee.crawler.notification.subject=Recording summary for tomorrow
+
--- /dev/null
+<programs>
+
+ <program>
+ <category>horror</category>
+ <action>notify</action>
+ <match field="description">horror</match>
+ </program>
+
+ <program>
+ <category>horror</category>
+ <action>notify</action>
+ <match>the.*ghost.*whisperer</match>
+ </program>
+
+ <program>
+ <category>films</category>
+ <action>notify</action>
+ <match field="keywords">film</match>
+ <match field="description">horror|actie|thriller</match>
+ </program>
+
+ <program>
+ <category>wetenschap</category>
+ <action>notify</action>
+ <match field="description">wetenschap</match>
+ </program>
+
+ <program>
+ <category>science fiction</category>
+ <action>notify</action>
+ <match field="description">sf-|(sci-fi)|(science fiction)</match>
+ </program>
+
+ <program>
+ <match>invasion</match>
+ </program>
+
+ <program>
+ <action>notify</action>
+ <category>documentaires</category>
+ <match>(zembla)|(uur.*wolf)|(andere tijden)|(de.*leugen.*regeert)</match>
+ </program>
+
+ <program>
+ <priority>20</priority>
+ <match>star.*gate</match>
+ </program>
+
+ <program>
+ <match>six.*feet.*under</match>
+ </program>
+
+ <program>
+ <priority>11</priority>
+ <match>battlestar</match>
+ </program>
+
+ <program>
+ <priority>10</priority>
+ <match>star trek</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>((dr)|(doct.*)).*who</match>
+ </program>
+
+ <program>
+ <priority>8</priority>
+ <match>little britain</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>the.*x.*files</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>buffy.*vampire.*slayer</match>
+ </program>
+
+ <program>
+ <action>notify</action>
+ <match>de.*wereld.*draait.*door</match>
+ </program>
+
+ <program>
+ <priority>8</priority>
+ <match>jag</match>
+ </program>
+
+ <program>
+ <priority>5</priority>
+ <match>shouf shouf</match>
+ </program>
+
+ <program>
+ <match>red dwarf</match>
+ </program>
+
+ <program>
+ <match>top gear</match>
+ </program>
+
+ <program>
+ <match>bedreigde.*paradijzen</match>
+ </program>
+
+ <program>
+ <match>wie is de baas</match>
+ </program>
+
+ <program>
+ <category>wetenschap</category>
+ <action>notify</action>
+ <match>brainiac</match>
+ </program>
+
+ <program>
+ <category>auto</category>
+ <match>wegmisbruikers|(blik.*op.*weg)</match>
+ </program>
+
+</programs>
--- /dev/null
+\r
+\r
+\r
+cd ../conf\r
+\r
+\r
+set CP=../lib/wamblee-crawler-kiss.jar;../lib/activation.jar;../lib/comons-beanutils-1.7.0.jar;../lib/commons-codec-1.3.jar\r
+set CP=%CP%;../lib/commons-email-1.0.jar;../lib/commons-httpclient-3.0.jar;../lib/commons-logging-1.0.2.jar;\r
+set CP=%CP%;../lib/dom4j-1.6.jar;../lib/jaxen-1.1-beta-4.jar;../lib/jtidy-4aug2000r7-dev.jar;log4j-1.2.9.jar;\r
+set CP=%CP%;../lib/mail.jar;../lib/spring-1.2.5.jar;../lib/wamblee-crawler-basic.jar;../lib/wamblee-crawler-kiss.jar;\r
+set CP=%CP%;../lib/wamblee-support.jar;../lib/xerces-2.4.0.jar\r
+\r
+\r
+java -classpath %CP% org.wamblee.crawler.kiss.main.KissCrawler config.xml programs.xml
+
--- /dev/null
+#!/bin/ksh
+
+cd $( dirname $0 )/../conf
+
+CP="."
+for i in ../lib/*.jar
+do
+ CP="$i:$CP"
+done
+
+set -x
+java -classpath $CP org.wamblee.crawler.kiss.main.KissCrawler \
+ config.xml programs.xml
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<title></title>
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Nederland 1 *</title>
+</head>
+<body bgcolor="#ffffff">
+d> <img src="../images/KiSS_Logo_small.gif"
+align="center" />
+<h1> Nederland 1 *</h1>
+
+<h2>What's on?</h2>
+
+<a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=0&now=1&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Right now</a> - 12:11, Monday 13th March<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=20&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)1~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Evening</a> - Starting 20:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=16&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)2~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Afternoon</a> - Starting 16:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=12&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)3~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Noon</a> - Starting 12:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=6&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)4~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Morning</a> - Starting 6:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=36&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Tomorrow</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=60&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)8~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Wednesday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=84&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)9~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Thursday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=108&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)10~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Friday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=132&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)11~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Saturday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=156&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)12~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Sunday</a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&page=0&station=0&tz=1&sel=0&back=$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:11, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<meta name="generator" content="Adobe GoLive" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<script language="JavaScript1.2" src="../popup15.js"
+type="text/javascript">
+</script>
+
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<div id="popuptitle"
+style="visibility:hidden;position:absolute;z-index:1000;top:-100">
+</div>
+
+<script language="JavaScript1.2" type="text/javascript">
+ Style[0]=["#ffffff","#6f7e8f","","","font-family:arial,helvetica,geneva,swiss,sunsans-regular",2,"#183457","#d0dce8","","","font-family:arial,helvetica,geneva,swiss,sunsans-regular",1,,,2,"#92AAC6",4,,,,,"",,,,]
+ var TipId="popuptitle"
+ var FiltersEnabled = 1
+ mig_clay()
+
+</script>
+
+<h1><img src="../images/KiSS_Logo.gif" align="right" /><a
+href="tvstart.php"><img border="0"
+src="../images/tvguide_logo.gif" /></a></h1>
+
+<br />
+
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td align="left">
+<h1><img
+src="http://epg.kml.kiss-technology.com/tv/logos/6_w60.gif"
+align="middle" hspace="6" /> Nederland 1</h1>
+
+<p></p>
+</td>
+</tr>
+</table>
+
+<table align="center" style="width: 400px; text-align: left;"
+border="0" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+<td style="vertical-align: bottom; width: 400px;"><img
+src="../images/tabs_none.gif" border="0" alt=""
+usemap="#tabs_Map" /><map id="tabs_Map" name="tabs_Map">
+<area shape="poly" alt="What's On Now?"
+coords="126,30, 118,0, 0,0, 0,30"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?reload=1&mode=3&station=0&view=0&now=1"
+ class="grid" />
+<area shape="poly" alt="Favorite Shows"
+coords="239,0, 247,30, 128,30, 120,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+<area shape="poly" alt="Movies"
+coords="241,0, 250,30, 309,30, 300,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+<area shape="poly" alt="Sports"
+coords="302,0, 311,30, 370,30, 362,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+</map> </td>
+</tr>
+</tbody>
+</table>
+
+<table class="tvlist" align="center" width="400" border="0"
+cellspacing="0" cellpadding="1">
+<tr height="25">
+<td align="left" bgcolor="black" colspan="2"><font
+color="white"><b> Wednesday 23rd </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 23:15 -
+00:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[0]=["Karels keuze","23:15 - 00:00<br>Documentaire Het schokkende verhaal van een Turkse familie uit Denemarken, die verscheurd raakt door eerwraak. Een minutieuze reconstructie van een hartverscheurend drama, verteld door het meisje om wie de ruzie begon en haar broer, die door haar vader gedwongen werd de trekker over te halen. Hij was toen dertien jaar en moest meedoen omdat hij te jong was om veroordeeld te kunne [...]"]
+</script>
+
+ <a onmouseover="stm(Text[0],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787582&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Karels keuze</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:00 -
+00:30 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[1]=["Nederland helpt","00:00 - 00:30<br>Serie reportages over Nederlanders, bekend of onbekend, die zich inzetten voor een goed doel."]
+</script>
+
+ <a onmouseover="stm(Text[1],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787583&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland helpt</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 00:30 -
+00:55 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[2]=["Man beet hond","00:30 - 00:55"]
+</script>
+
+ <a onmouseover="stm(Text[2],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787584&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:55 -
+06:45 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[3]=["Nacht-tv: Netwerk herhaling","00:55 - 06:45"]
+</script>
+
+ <a onmouseover="stm(Text[3],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787585&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 06:45 -
+07:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[4]=["Nederland in beweging","06:45 - 07:00<br>Gymnastiek met als doel Nederlanders in beweging te krijgen. Met voedingstips, de favoriete sport van een bekende Nederlander en sportieve uitgaanstips."]
+</script>
+
+ <a onmouseover="stm(Text[4],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849473&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 07:00 -
+09:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[5]=["NOS-Journaal","07:00 - 09:00<br>Gevolgd door herhalingen."]
+</script>
+
+ <a onmouseover="stm(Text[5],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849474&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:00 -
+09:10 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[6]=["NOS-Journaal","09:00 - 09:10"]
+</script>
+
+ <a onmouseover="stm(Text[6],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849475&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:10 -
+09:30 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[7]=["Nederland in beweging","09:10 - 09:30<br>Gymnastiek met als doel Nederlanders in beweging te krijgen. Met voedingstips, de favoriete sport van een bekende Nederlander en sportieve uitgaanstips."]
+</script>
+
+ <a onmouseover="stm(Text[7],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849476&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:30 -
+09:55 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[8]=["That's the question","09:30 - 09:55<br>Quiz waarin twee deelnemers zo snel mogelijk een vraag moeten raden. Aan de hand van het beantwoorden van vragen worden er letters van de vraag zichtbaar. Met het goed beantwoorden van de vragen verdient de kandidaat seconden waarmee hij/zij later de finale speelt."]
+</script>
+
+ <a onmouseover="stm(Text[8],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849477&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:55 -
+11:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[9]=["TROS Zomeravondcafé","09:55 - 11:00<br>Live programma vanaf het binnenplein van het TROS-gebouw. Iedere dag staat in het teken van een ander zomers thema. Bert Kuizenga praat met een bekende hoofdgast, die aansluit bij het thema van de dag. De actualiteit wordt behandeld door Angela Esajas. Verder twee muzikale acts en de kookrubriek met Ad Janssen. Bovendien is er twee keer per week een optreden van psycho-illusionis [...]"]
+</script>
+
+ <a onmouseover="stm(Text[9],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849478&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>TROS Zomeravondcafé</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 11:00 -
+11:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[10]=["Andere tijden","11:00 - 11:30<br>Geschiedenismagazine Bij het hardrijden op de schaats deed Nederland tijdens de Olympische Winterspelen weer op veel fronten mee, maar bij het kunstrijden hadden we niets meer te zoeken. Dat is wel eens anders geweest; in 1964 won Sjoukje Dijkstra in Innsbrück een gouden medaille. Over haar en haar hartsvriendin èn rivale Joan Haanappel gaat deze aflevering."]
+</script>
+
+ <a onmouseover="stm(Text[10],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849479&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Andere tijden</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 11:30 -
+12:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[11]=["De vloer op","11:30 - 12:00<br>Acteurs improviseren in het Bimhuis. -Vervangster gezocht. Met Carly Wijs, Hadewych Minis en Gijs Scholten van Aschat. Carly heeft nog ongeveer twee maanden te leven. Ze gaat naar de secretaresse van haar man en vraagt haar of zij na haar dood haar plaats wil innemen. In alle opzichten. -Burka zoekt imam. Met Leopold Witte en Pierre Bokma. Pierre is burgemeester van een kleine Li [...]"]
+</script>
+
+ <a onmouseover="stm(Text[11],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849480&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De vloer op</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:00 -
+12:10 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[12]=["NOS-Journaal","12:00 - 12:10"]
+</script>
+
+ <a onmouseover="stm(Text[12],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849481&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 12:10 -
+12:40 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[13]=["Man beet hond","12:10 - 12:40<br>Het beste van het afgelopen jaar."]
+</script>
+
+ <a onmouseover="stm(Text[13],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849482&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:40 -
+13:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[14]=["Lingo","12:40 - 13:00<br>Woordspel waarin spelers zesletterwoorden moeten raden en deelnemers aan de Postcode Loterij met de PostcodeLingo-kaart mee kunnen spelen en waarin de bekendmaking van de dagpostcodes voor de Lingospelers thuis plaatsvindt."]
+</script>
+
+ <a onmouseover="stm(Text[14],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849483&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Lingo</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:00 -
+13:10 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[15]=["NOS-Journaal","13:00 - 13:10"]
+</script>
+
+ <a onmouseover="stm(Text[15],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849484&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 13:10 -
+13:15 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[16]=["NOS-Sportjournaal","13:10 - 13:15"]
+</script>
+
+ <a onmouseover="stm(Text[16],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849485&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Sportjournaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:15 -
+14:05 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[17]=["Warzone: Congo","13:15 - 14:05<br>Documentaire waarin het leven van jongeren in conflictgebieden op een indringende manier in beeld wordt gebracht. In Congo worden Gideon van Aartsen en zijn 25-jarige reisgenote Nynke Douma geconfronteerd met tienermoeders en heksenkinderen. In dit land vinden veel verkrachtingen plaats, waardoor de grote hoeveelheid tienermoeders kan worden verklaart. Ook onderzoeken ze het feno [...]"]
+</script>
+
+ <a onmouseover="stm(Text[17],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849486&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Warzone: Congo</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 14:05 -
+14:35 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[18]=["Wroeten","14:05 - 14:35<br>12-delige Tuinprogramma waarin Arjan Ederveen bevlogen verslag uitbrengt van wat er gebeurt in de tuin bij zijn boerderij: er groeien gewone en ongewone groenten, er bloeien bloemen en planten en elke week wordt er een logeerdier gebracht door een boer die wel eens met vakantie wil. Arjan wordt bijgestaan door Heilien Tonckens, expert in ecologisch tuinieren, en tuinman Hans Enge [...]"]
+</script>
+
+ <a onmouseover="stm(Text[18],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849487&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wroeten</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 14:35 -
+15:14 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[19]=["Spraakmakende zaken","14:35 - 15:14<br>8-delige Serie gesprekken over actuele kwesties. Paul Rosenmöller ontvangt zijn gasten in Paviljoen Het Oosten in Amsterdam, waar hij met hen praat over brandende kwesties uit het recente verleden. Het is behoorlijk bijzonder dat SGP-leider Bas van der Vlies tekst en uitleg komt geven over de vrouwenkwestie in de SGP. SGP-ers zijn uit overtuiging immers niet zo dol op telev [...]"]
+</script>
+
+ <a onmouseover="stm(Text[19],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849488&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Spraakmakende zaken</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 15:14 -
+15:16 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[20]=["Wilde Ganzen","15:14 - 15:16"]
+</script>
+
+ <a onmouseover="stm(Text[20],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849489&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wilde Ganzen</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 15:16 -
+16:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[21]=["Opsporing verzocht","15:16 - 16:00<br>Opsporingsprogramma i.s.m. politie en justitie voor een veiliger samenleving."]
+</script>
+
+ <a onmouseover="stm(Text[21],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849490&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Opsporing verzocht</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 16:00 -
+16:05 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[22]=["NOS-Journaal","16:00 - 16:05"]
+</script>
+
+ <a onmouseover="stm(Text[22],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849491&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 16:05 -
+17:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[23]=["Denkend aan Showroom","16:05 - 17:00<br>Herinneringen aan de kleurrijke mensen uit het spraakmakende programma Showroom (1977-1983)."]
+</script>
+
+ <a onmouseover="stm(Text[23],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849492&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Denkend aan Showroom</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:00 -
+17:05 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[24]=["NOS-Journaal","17:00 - 17:05"]
+</script>
+
+ <a onmouseover="stm(Text[24],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849493&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 17:05 -
+17:35 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[25]=["God voor...","17:05 - 17:35<br>Snel interviewprogramma over langzame zaken. Surinamers over feesten en God."]
+</script>
+
+ <a onmouseover="stm(Text[25],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849494&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>God voor...</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:35 -
+18:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[26]=["Himalaya with Michael Palin","17:35 - 18:30<br>6-delige Serie reportages Nadat Michael Palin eerder de uitdagingen van zeeën, polen en woestijnen is aangegaan, vormen de hoogste bergen ter wereld voor hem een natuurlijk doel. Hij volgt de Himalaya over de hele lengte van het gebergte, van de grens tussen Pakistan en Afghanistan door India, Nepal en Tibet en Yunnan in China. Waarna hij opnieuw de bergen oversteekt, naar A [...]"]
+</script>
+
+ <a onmouseover="stm(Text[26],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849495&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Himalaya with Michael Palin</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 18:30 -
+19:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[27]=["That's the question","18:30 - 19:00<br>Quiz waarin twee deelnemers zo snel mogelijk een vraag moeten raden. Aan de hand van het beantwoorden van vragen worden er letters van de vraag zichtbaar. Met het goed beantwoorden van de vragen verdient de kandidaat seconden waarmee hij/zij later de finale speelt."]
+</script>
+
+ <a onmouseover="stm(Text[27],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849496&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 19:00 -
+19:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[28]=["Man beet hond","19:00 - 19:30<br>Het beste van het afgelopen jaar."]
+</script>
+
+ <a onmouseover="stm(Text[28],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849497&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 19:30 -
+20:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[29]=["De confrontatie","19:30 - 20:00<br>Edwin is een goeduitziende jongen van twintig jaar. Hij heeft een aangeboren botafwijking en mist daardoor zijn bovenbenen. Hij kan kleine stukjes lopen, maar is in principe rolstoelgebonden. Vanaf zijn dertiende is Edwin rolstoelbasketbal gaan spelen. Eerst nog op lokaal niveau, maar na een inschrijving voor selectietrainingen in 2005 werd hij gescout voor het Nederlandse nationa [...]"]
+</script>
+
+ <a onmouseover="stm(Text[29],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849498&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De confrontatie</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 20:00 -
+20:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[30]=["NOS-Journaal","20:00 - 20:30"]
+</script>
+
+ <a onmouseover="stm(Text[30],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849499&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 20:30 -
+21:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[31]=["Netwerk","20:30 - 21:00<br>Actualiteiten van EO, KRO en NCRV."]
+</script>
+
+ <a onmouseover="stm(Text[31],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849500&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Netwerk</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 21:00 -
+22:35 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[32]=["KRO Detectives: Judge John Deed","21:00 - 22:35<br>Misdaadserie Een jonge vrouw wordt bruut doodgeslagen. Gary Patterson bekent de moord. Hij heeft echter de mentale leeftijd van een jonge tiener en als hij zijn bekentenis ook nog intrekt, wordt het moeilijk de waarheid te achterhalen. Intussen onderzoekt Deed ook een zaak van grootschalige hypotheekfraude. Hierbij blijken procureurs betrokken. Met zijn hardnekkige onderzoek lijk [...]"]
+</script>
+
+ <a onmouseover="stm(Text[32],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849501&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>KRO Detectives: Judge John Deed</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#106F10">
+<div align="center"><font color="#FFFFFF"><b> 22:35 -
+23:05 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[33]=["Dochters + moeders","22:35 - 23:05<br>Ze heeft je gebaard, ze is lief, af en toe wil je haar het liefst achter het behang plakken maar het blijft toch je moeder. Ze is eigenwijs, komt altijd met de foute vriendjes thuis en luistert nooit maar het blijft je dochter. Is er een uniekere band dan die tussen dochter en moeder?."]
+</script>
+
+ <a onmouseover="stm(Text[33],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849502&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Dochters + moeders</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:05 -
+23:32 </b></font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[34]=["De God van Nederland","23:05 - 23:32<br>8-delige Serie reportages waarin de barometerstand van de wonderbaarlijke terugkeer van de christelijke God in de Hollandse polder gepeild wordt en die op zoek gaat naar het Nieuw Religieus Peil. Een serie over verlangen naar idealen, geborgenheid en gemeenschap en een zinvol bestaan als rituele dagsluiting voor gelovigen en ongelovigen, zoekers en zieners, dolende zielen en vast [...]"]
+</script>
+
+ <a onmouseover="stm(Text[34],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849503&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De God van Nederland</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:32 -
+23:40 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[35]=["Wilde Ganzen","23:32 - 23:40"]
+</script>
+
+ <a onmouseover="stm(Text[35],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849504&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wilde Ganzen</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:40 -
+00:05 </b></font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[36]=["Man beet hond","23:40 - 00:05"]
+</script>
+
+ <a onmouseover="stm(Text[36],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849505&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 00:05 -
+06:45 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[37]=["Nacht-tv: Netwerk herhaling","00:05 - 06:45"]
+</script>
+
+ <a onmouseover="stm(Text[37],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849506&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></td>
+</tr>
+</table>
+
+<br />
+
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td rowspan="3"> </td>
+<td style="width: 140px; vertical-align: bottom;"><img
+src="../images/legend_green.gif" /> Currently
+showing<br />
+</td>
+<td style="width: 140px; vertical-align: bottom;"><img
+src="../images/legend_orange.gif" /> Favorite<br />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: middle;"><img
+src="../images/legend_blue.gif" /> Future showing<br />
+</td>
+<td style="vertical-align: middle;"><img
+src="../images/legend_red.gif" /> Scheduled
+recording<br />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: middle;"><img
+src="../images/legend_purple.gif" /> Movie<br />
+</td>
+<td style="vertical-align: middle;"> </td>
+</tr>
+</table>
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=2&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/back.gif" alt="Back" border="0"
+align="middle" /></a><a href="tvstart.php"><img
+src="../images/home.gif" alt="Home" border="0"
+align="middle" /></a><a href="../logout.php"><img
+src="../images/logout.gif" alt="Logout" border="0"
+align="middle" /></a> 22:38, Wednesday 23rd August</td>
+</tr>
+</table>
+</body>
+</html>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name="" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> Monday 21st </time></action>
+
+ <action name="NCRV Dokument: Het recht om te sterven" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787547&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 23:00 -23:45 </time></action>
+
+ <action name="Apocalyps Vietnam" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787548&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 23:45 -00:40 </time></action>
+
+ <action name="Man beet hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787549&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 00:40 -01:10 </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787550&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 01:10 -06:45 </time></action>
+
+ <action name="" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> Monday 21st </time></action>
+
+ <action name="" type="program-info" reference=""><time> 23:13, Monday 21st August</time></action>
+
+ </channel-right-now>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<meta name="generator" content="Adobe GoLive" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="right" />
+
+<h1>Nederland 1</h1>
+
+<table class="tvlist" align="center" width="100%" border="0"
+cellspacing="0" cellpadding="1">
+<tr height="25">
+<td align="left" bgcolor="black"><font
+color="white"><b> Monday 21st </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright_small.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 22:30 -
+00:15 </font></div>
+</td>
+<td align="left" class="listCell2"><a class="movie"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777728676&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>KRO Filmtheater: Hollywood ending</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:15 -
+06:45 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777728677&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Tekst tv</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 06:45 -
+07:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787518&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 07:00 -
+09:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787519&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:00 -
+09:10 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787520&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:10 -
+09:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787521&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:30 -
+10:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787522&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 10:00 -
+11:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787523&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>TROS Muziekfeest in de ArenA 2004</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 11:00 -
+11:25 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787524&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Hello Goodbye</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 11:25 -
+12:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787525&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wat Zou JIJ Doen?</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:00 -
+12:10 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787526&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 12:10 -
+12:40 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787527&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Taxi</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:40 -
+13:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787528&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Lingo</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:00 -
+13:10 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787529&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 13:10 -
+13:20 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787530&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Sportjournaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:20 -
+14:05 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787531&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Warzone: Zuid-Afrika</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 14:05 -
+15:20 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787532&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Tien maal Mozart</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 15:20 -
+16:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787533&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Kruispunt</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 16:00 -
+16:05 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787534&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 16:05 -
+17:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787535&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Oppassen & wegwezen</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:00 -
+17:05 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787536&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 17:05 -
+17:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787537&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Schepper & co</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:30 -
+18:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787538&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Himalaya with Michael Palin</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 18:30 -
+18:55 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787539&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 18:55 -
+19:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787540&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 19:30 -
+20:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787541&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Ingang Oost</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 20:00 -
+20:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787542&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 20:30 -
+21:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787543&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Netwerk</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 21:00 -
+22:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787544&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Memories Tour d'Amour</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 22:00 -
+22:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787545&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Hello Goodbye</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 22:30 -
+23:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787546&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Villa historica</b></a></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#106F10">
+<div align="center"><font color="#FFFFFF"><b> 23:00 -
+23:45 </b></font></div>
+</td>
+<td align="left" bgcolor="#106F10"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787547&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NCRV Dokument: Het recht om te sterven</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:45 -
+00:40 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787548&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Apocalyps Vietnam</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 00:40 -
+01:10 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787549&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 01:10 -
+06:45 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787550&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></b></font></td>
+</tr>
+
+<tr height="25">
+<td align="left" bgcolor="black"><font
+color="white"><b> Monday 21st </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright_small.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+</table>
+
+<table align="center" width="100%" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><br />
+<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=2&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/back_small.gif" alt="Back" border="0"
+align="center" /></a> <a href="tvstart.php"><img
+src="../images/home_small.gif" alt="Home" border="0"
+align="center" /></a> <a href="../logout.php"><img
+src="../images/logout_small.gif" alt="Logout" border="0"
+align="center" /></a> 23:13, Monday 21st August</td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name=">>" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time> </time></action>
+
+ <action name="Wintertijd" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 23:55 - 00:40 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhalingen" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+00:50 - 06:15 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:45 - 06:59 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:59 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 07:00 - 07:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:10 - 07:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:30 - 07:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:40 - 08:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:00 - 08:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:10 - 08:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 08:30 - 08:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:40 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:00 - 09:10 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:10 - 09:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:30 - 09:55 - </time></action>
+
+ <action name="Schoondochter gezocht" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:55 - 10:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 10:50 - 11:15 - </time></action>
+
+ <action name="Appeltje voor de dorst" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+11:15 - 12:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:00 - 12:10 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:10 - 12:35 - </time></action>
+
+ <action name="Voor alle fans: Drukwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:35 - 12:57 - </time></action>
+
+ <action name="Trekking Lingo" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:57 - 13:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:00 - 13:10 - </time></action>
+
+ <action name="NOS-Sportjournaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:10 - 13:20 - </time></action>
+
+ <action name="Buitenhof" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:20 - 14:15 - </time></action>
+
+ <action name="Hoge bomen in de misdaad" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:15 - 14:55 - </time></action>
+
+ <action name="AVRO Dierenpark" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:55 - 15:20 - </time></action>
+
+ <action name="Kruispunt" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>15:20 - 16:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:00 - 16:05 - </time></action>
+
+ <action name="Helden van nu: Vrijwilligers in de gezondheidszorg" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:05 - 16:30 - </time></action>
+
+ <action name="Leven met verlies" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:30 - 17:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:00 - 17:10 - </time></action>
+
+ <action name="Schepper & co" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:10 - 17:35 - </time></action>
+
+ <action name="MAX & Catherine" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:35 - 18:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:30 - 18:55 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:55 - 19:25 - </time></action>
+
+ <action name="Ingang Oost" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>19:25 - 20:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:00 - 20:30 - </time></action>
+
+ <action name="Netwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:30 - 21:05 - </time></action>
+
+ <action name="Memories" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>21:05 - 22:05 - </time></action>
+
+ <action name="Keyzer & De Boer Advocaten" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:05 - 22:55 - </time></action>
+
+ <action name="NCRV Dokument: Een familie van vaders" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:55 - 23:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>23:50 - 00:20 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:20 - 00:50 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:50 - 06:45 - </time></action>
+
+
+BackHomeLogout</channel-right-now>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>Nederland 1</h1>
+
+<b>Monday 13th</b><br />
+<b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+<br />
+ 23:55 - 00:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Wintertijd</a><br />
+00:50 - 06:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nacht-tv: Netwerk herhalingen</a><br />
+06:45 - 06:59 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland in beweging</a><br />
+06:59 - 09:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+ 07:00 - 07:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+07:10 - 07:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+07:30 - 07:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+07:40 - 08:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+08:00 - 08:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+08:10 - 08:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+ 08:30 - 08:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+08:40 - 09:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+09:00 - 09:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+09:10 - 09:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland in beweging</a><br />
+09:30 - 09:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+That's the question</a><br />
+09:55 - 10:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Schoondochter gezocht</a><br />
+ 10:50 - 11:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Blauw bloed</a><br />
+11:15 - 12:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Appeltje voor de dorst</a><br />
+<i>12:00 - 12:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>12:10 - 12:35 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>12:35 - 12:57 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Voor alle fans: Drukwerk</a></i><br />
+<i>12:57 - 13:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Trekking Lingo</a></i><br />
+ <i>13:00 - 13:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>13:10 - 13:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Sportjournaal</a></i><br />
+<i>13:20 - 14:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Buitenhof</a></i><br />
+<i>14:15 - 14:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Hoge bomen in de misdaad</a></i><br />
+<i>14:55 - 15:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+AVRO Dierenpark</a></i><br />
+<i>15:20 - 16:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Kruispunt</a></i><br />
+ <i>16:00 - 16:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>16:05 - 16:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Helden van nu: Vrijwilligers in de gezondheidszorg</a></i><br />
+<i>16:30 - 17:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Leven met verlies</a></i><br />
+<i>17:00 - 17:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>17:10 - 17:35 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Schepper & co</a></i><br />
+ <i>17:35 - 18:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+MAX & Catherine</a></i><br />
+<i>18:30 - 18:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+That's the question</a></i><br />
+<i>18:55 - 19:25 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>19:25 - 20:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Ingang Oost</a></i><br />
+<i>20:00 - 20:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+ <i>20:30 - 21:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Netwerk</a></i><br />
+<i>21:05 - 22:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Memories</a></i><br />
+<i>22:05 - 22:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Keyzer & De Boer Advocaten</a></i><br />
+<i>22:55 - 23:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NCRV Dokument: Een familie van vaders</a></i><br />
+<i>23:50 - 00:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Blauw bloed</a></i><br />
+ <i>00:20 - 00:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>00:50 - 06:45 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nacht-tv: Netwerk herhaling</a></i><br />
+<br />
+ <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=1&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:04, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<favorite-channels>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 1" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 2" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)1~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 3" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)2~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Net5" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)3~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL4" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)4~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="SBS6" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)5~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL5" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)6~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Discovery Channel"
+ type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)7~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL7" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)8~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Veronica" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)9~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="MTV" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)10~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="TheBox" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)11~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Eurosport" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)12~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="CNN" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)13~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="BBC1" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)14~back)$tvhtml$tvstart.php(tz)2"
+ />
+</favorite-channels>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org"/>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1"/>
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css"/>
+ <link rel="shortcut icon" href="../favicon.ico"/>
+ <title>KiSS - Favorite Channels</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <h1>
+ <img src="../images/KiSS_Logo.gif" align="right"/>
+ <a href="tvstart.php">
+ <img border="0" src="../images/tvguide_logo.gif"/>
+ </a>
+ </h1>
+
+ <br/>
+
+
+ <table align="center" width="400" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td align="left">
+ <h1>Favorite Channels</h1>
+
+ <p/>
+ </td>
+ </tr>
+ </table>
+
+ <table align="center" style="width: 400; text-align: left;" border="0" cellpadding="0"
+ cellspacing="0">
+ <tbody>
+ <tr>
+ <td style="vertical-align: bottom; width: 400px;">
+ <img src="../images/tabs_none.gif" alt="" usemap="#tabs_Map" border="0"/>
+ <map id="tabs_Map" name="tabs_Map">
+ <area shape="poly" alt="What's On Now?" coords="126,30, 118,0, 0,0, 0,30"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?reload=1&mode=3&station=0&view=0&now=1"
+ class="grid"/>
+ <area shape="poly" alt="Favorite Shows" coords="239,0, 247,30, 128,30, 120,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ <area shape="poly" alt="Movies" coords="241,0, 250,30, 309,30, 300,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ <area shape="poly" alt="Sports" coords="302,0, 311,30, 370,30, 362,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ </map>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table class="tv" align="center" width="400" border="0" cellspacing="1" cellpadding="1">
+ <tr height="25">
+ <td bgcolor="black" align="right" colspan="3">
+ <font color="white"> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=1&tz=2&back=$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowleft.gif" alt="Prev" border="0" align="texttop"/>
+ </a> <b>Page 1/2</b> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=1&tz=2&back=$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowright.gif" alt="Next" border="0" align="texttop"/>
+ </a> <br/>
+ </font>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/6_h30.gif" align="middle"
+ /> <b>NL1</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 1</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/2_h30.gif" align="middle"
+ /> <b>NL2</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 2</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/5_h30.gif" align="middle"
+ /> <b>NL3</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 3</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/4_h30.gif" align="middle"
+ /> <b>NET5</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <b>Net5</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/9_h30.gif" align="middle"
+ /> <b>RTL4</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL4</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/7_h30.gif" align="middle"
+ /> <b>SBS6</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <b>SBS6</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/3_h30.gif" align="middle"
+ /> <b>RTL5</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL5</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/99_h30.gif" align="middle"
+ /> <b>DISC</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <b>Discovery Channel</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/8_h30.gif" align="middle"
+ /> <b>RTL7</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL7</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/1_h30.gif" align="middle"
+ /> <b>VERO</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <b>Veronica</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/177_h30.gif" align="middle"
+ /> <b>MTV</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <b>MTV</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/665_h30.gif" align="middle"
+ /> <b>BOX</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <b>TheBox</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/94_h30.gif" align="middle"
+ /> <b>ESPO</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <b>Eurosport</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/470_h30.gif" align="middle"
+ /> <b>CNN</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <b>CNN</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/184_h30.gif" align="middle"
+ /> <b>BBC1</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <b>BBC1</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+ </table>
+
+ <table align="center" width="400" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td><br/>
+ <br/>
+ <a href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=2">
+ <img src="../images/back.gif" alt="Back" border="0" align="middle"/>
+ </a><a href="tvstart.php">
+ <img src="../images/home.gif" alt="Home" border="0" align="middle"/>
+ </a><a href="../logout.php">
+ <img src="../images/logout.gif" alt="Logout" border="0" align="middle"/>
+ </a> 17:15, Wednesday 23rd August</td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<favorite-channels> << >><action name="Nederland 1"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Nederland 2"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Nederland 3"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Net5"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL4"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="SBS6"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL5"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)6~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Discovery Channel"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)7~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL7"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Veronica"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Eurosport"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="MTV"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="TheBox"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="CNN"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)13~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="BBC1"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)14~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="BBC2"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=185&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)15~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/> << >>BackHomeLogout</favorite-channels>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Favorite Channels</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>Favorite Channels</h1>
+
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+<<</a> <b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+<br />
+ <b>NL1</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland 1</a><br />
+<b>NL2</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Nederland 2</a><br />
+<b>NL3</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1">
+Nederland 3</a><br />
+ <b>NET5</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1">
+Net5</a><br />
+<b>RTL4</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1">
+RTL4</a><br />
+<b>SBS6</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1">
+SBS6</a><br />
+<b>RTL5</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)6~back)$tvhtml$tvstart.php(tz)1">
+RTL5</a><br />
+ <b>DISC</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)7~back)$tvhtml$tvstart.php(tz)1">
+Discovery Channel</a><br />
+<b>RTL7</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1">
+RTL7</a><br />
+<b>VERO</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1">
+Veronica</a><br />
+<b>ESPO</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1">
+Eurosport</a><br />
+ <b>MTV</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1">
+MTV</a><br />
+<b>BOX</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1">
+TheBox</a><br />
+<b>CNN</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)13~back)$tvhtml$tvstart.php(tz)1">
+CNN</a><br />
+<b>BBC1</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)14~back)$tvhtml$tvstart.php(tz)1">
+BBC1</a><br />
+ <b>BBC2</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=185&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)15~back)$tvhtml$tvstart.php(tz)1">
+BBC2</a><br />
+ <br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+<<</a> <b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=1">Back</a> ] [ <a
+ href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:08, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Favorite Channels</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1> Favorite Channels</h1>
+
+<h2>What's on?</h2>
+
+<a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Right now</a> - 11:43, Monday 13th March<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=20&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Evening</a> - Starting 20:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=16&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1">
+Afternoon</a> - Starting 16:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=12&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1">
+Noon</a> - Starting 12:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=6&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1">
+Morning</a> - Starting 6:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=36&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1">
+Tomorrow</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=60&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Wednesday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=84&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Thursday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=108&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Friday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=132&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Saturday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=156&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Sunday</a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=1">Back</a> ] [ <a
+ href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 11:43, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+ <head>\r
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />\r
+ <meta http-equiv="content-type"\r
+ content="text/html;charset=iso-8859-1" />\r
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css" />\r
+ <link rel='shortcut icon' href='../favicon.ico' />\r
+ <title>KiSS - TV Guide</title>\r
+ </head>\r
+ <body bgcolor="#ffffff">\r
+ <img src="../images/KiSS_Logo.gif" align="right" /><br />\r
+ <br />\r
+ <br />\r
+ \r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td align="left"><img src="../images/tvguide_logo.gif" /><br />\r
+ <br />\r
+ <br />\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td><b>Welcome</b> <u>sf2np2ln9no1</u> /\r
+ <u>web@brakkee.org</u> [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2">\r
+ Change</a> email ]<br />\r
+ <br />\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table class="tvstart" width="400" border="0" cellspacing="0"\r
+ cellpadding="1" align="center">\r
+ <tr>\r
+ <td align="center" height="25" bgcolor="black">\r
+ <div align="center"><font color="white"><b> Favorite\r
+ Channels </b></font></div>\r
+ </td>\r
+ <td align="center" bgcolor="black" width="10"> </td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font color="white"><b> Favorite\r
+ Shows </b></font></div>\r
+ </td>\r
+ <td align="center" bgcolor="black" width="10"> </td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font\r
+ color="white"><b> Movies </b></font></div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="10">\r
+ <td align="center" height="10"></td>\r
+ <td align="center" width="10" height="10"></td>\r
+ <td align="center" height="10"></td>\r
+ <td align="center" width="10" height="10"></td>\r
+ <td align="center" height="10"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on now?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Search a show</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center"></td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Favorites</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Favorites</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font\r
+ color="white"><b> Sports </b></font></div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center"></td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Add a favorite</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td>\r
+ <p><br />\r
+ Recordings to be sent to the player: <b>0</b> [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2">\r
+ View</a> ] [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2">Manual\r
+ recording</a> ]<br />\r
+ <br />\r
+ </p>\r
+ \r
+ <hr />\r
+ <br />\r
+ <b>Time change:</b> In order to see the correct time in the KiSS TV\r
+ Guide, do the following:\r
+ \r
+ <ol>\r
+ <li>On your player press SETUP and set your timezone to reflect\r
+ summer time, for example for mainland Western Europe this should\r
+ be: "CEST (GMT+2)", UK and Ireland: "BST (GMT+1)", Finland and\r
+ Eastern Europe: "EEST (GMT+3)".</li>\r
+ \r
+ <li>Access the TV Guide at least once via your player.</li>\r
+ \r
+ <li>The KiSS TV Guide will now display the correct time for your tv\r
+ programs both via your player and through the web.</li>\r
+ </ol>\r
+ \r
+ <br />\r
+ <hr />\r
+ <br />\r
+ KML favorites: <b>0</b> [ <a\r
+ href="http://epg.kml.kiss-technology.com/favorites/favhtml.php?tz=2&back=$tvhtml$tvstart.php(tz)2">\r
+ View</a> ]<br />\r
+ <br />\r
+ <br />\r
+ <a href="../logout.php"><img src="../images/logout_solo.gif"\r
+ alt="logout" border="0" align="middle" /></a> 16:29,\r
+ Wednesday 23rd August</td>\r
+ </tr>\r
+ </table>\r
+ </body>\r
+</html>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<login>
+ <action name=" unknown Change"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2"
+ type=" unknown Change"/>
+ <action name=" unknown What's on now?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on now?"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown Favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Favorites"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown Search a show"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Search a show"/>
+ <action name=" unknown Favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Favorites"/>
+ <action name=" unknown Add a favorite"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Add a favorite"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name="view-recordings"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type="view-recordings"/>
+ <action name="manual-recording"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2"
+ type="manual-recording"/>
+ <action name=" unknown "
+ reference="../logout.php"
+ type=" unknown "/>
+</login>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+
+<h1>TV Guide</h1>
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><b>Welcome</b> <u>sf2np2ln9no1</u> /
+<u>web@brakkee.org</u> [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2">
+Change</a> email ]<br />
+<br />
+ </td>
+</tr>
+</table>
+
+<table class="tvstart" width="100%" border="0" cellspacing="0"
+cellpadding="1" align="center">
+<tr>
+<td align="center" height="25" bgcolor="black">
+<div align="center"><font color="white"><b> Favorite
+Channels </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on now?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Favorites</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font color="white"><b> Favorite
+Shows </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Search a show</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Favorites</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Add a favorite</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font
+color="white"><b> Movies </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font
+color="white"><b> Sports </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center"></td>
+</tr>
+</table>
+
+<table align="center" width="100%" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td>
+<p><br />
+Recordings to be sent to the player: 0 [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2">
+View</a> ] [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2">Manual
+recording</a> ]<br />
+<br />
+ <a href="../logout.php"><img src="../images/logout_small.gif"
+alt="logout" border="0" align="center" /></a> 22:19, Monday
+21st August</p>
+</td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<login>
+ <action
+ name=" unknown Change"
+ type=" unknown Change"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=1"/>
+ <action name="channels-whats-on-now" type="channels-whats-on-now"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="channels-whats-on" type="channels-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="channels-favorites" type="channels-favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-whats-on" type="shows-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-search" type="shows-search"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-favorites" type="shows-favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-add-favorite" type="shows-add-favorite"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="movies-whats-on" type="movies-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="sports-whats-on" type="sports-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="view-recordings" type="view-recordings"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="manual-recording" type="manual-recording"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=1"/>
+ <action name="logout" type="logout" reference="../logout.php"/>
+</login>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />
+ <meta http-equiv="content-type"
+ content="text/html;charset=iso-8859-1" />
+ <title>KiSS - TV Guide</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <img src="../images/KiSS_Logo_small.gif" align="center" />
+
+ <h1>TV Guide</h1>
+
+ <b>Welcome</b> <u>sf2np2ln9no1</u> /
+ <u>erik@brakkee.org</u> [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=1">
+ Change</a> email ]<br />
+ <br />
+
+
+ <h2>Favorite Channels</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on now?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Favorites</a>
+
+ <h2>Favorite Shows</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Search a show</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Favorites</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Add a favorite</a>
+
+ <h2>Movies</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a>
+
+ <h2>Sports</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a>
+
+ <p><br />
+ Recordings to be sent to the player: 0<br />
+ [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ View</a> ] [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=1">Manual
+ recording</a> ]<br />
+ <br />
+ [ <a href="../logout.php">Logout</a> ]<br />
+ 21:09, Sunday 12th March</p>
+ </body>
+ </html>
+
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />
+ <meta content="text/html;charset=iso-8859-1"
+ http-equiv="content-type" />
+ <link href="kiss.css" type="text/css" rel="STYLESHEET" />
+ <link rel='shortcut icon' href='favicon.ico' />
+ <title>KiSS Technology Online Portal</title>
+ <script type="text/javascript" language="JavaScript">
+ //<![CDATA[
+ function placeFocus() {
+ if (document.forms.length > 0) {
+ var field = document.forms[0];
+ for (i = 0; i < field.length; i++) {
+ if ((field.elements[i].type == "text") || (field.elements[i].type == "textarea") || (field.elements[i].type.toString().charAt(0) == "s")) {
+ document.forms[0].elements[i].focus();
+ break; } } } }
+ function placeFocusP() {
+ if (document.forms.length > 0) {
+ var field = document.forms[0];
+ for (i = 0; i < field.length; i++) {
+ if ((field.elements[i].type == "password")) {
+ document.forms[0].elements[i].focus();
+ break; } } } }
+ //]]>
+ </script>
+ </head>
+ <body bgcolor="#ffffff" onload="placeFocus()">
+ <div align="center">
+ <h2><img src="images/kiss_logo_login.gif"
+ align="middle" /> Web Services:<br />
+ <img src="images/tvguide_logo.gif" align="middle" /><br />
+ </h2>
+ </div>
+
+ <table align="center" border="0" cellpadding="0" cellspacing="0"
+ style="width: 240px;">
+ <tbody>
+ <tr>
+ <td style="text-align: left;">
+ <h2> Login</h2>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <br />
+
+
+ <form name="FormName" method="post" action="login_core.php"
+ id="FormName"><input type="hidden" name="token"
+ value="e1c6b500600a0a2ba585ae52338a817f" />
+
+ <table align="center" border="0" cellpadding="0" cellspacing="0"
+ background="images/login_background.gif"
+ style="width: 240px; height: 240px;">
+ <tbody>
+ <tr>
+ <td style="vertical-align: top;"><br />
+ </td>
+ <td style="vertical-align: top;"><br />
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align: right; width: 100px;">Player ID <br />
+ or Email: </td>
+ <td style="width: 180px;"><input type="text" maxlength="50"
+ size="20" name="user" value="" /> *<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align: right; width: 100px;">Password: </td>
+ <td><input type="password" maxlength="10" size="8" name="passwd" />
+ **<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="checkbox" value="1" name="SavePlayerID" /> Save
+ PlayerID ***<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="radio" checked="checked" value="GraphicMode"
+ name="GMode" />Desktop mode<br />
+ <input type="radio" value="MobileMode" name="GMode" />Mobile
+ mode<br />
+ <input type="radio" value="TextMode" name="GMode" />Text mode</td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="submit" value="Login" name="submit" /></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><br />
+ </td>
+ <td style="vertical-align: top;"><br />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </form>
+
+ <center>[ <a href="l.php">Text version</a> ]</center>
+
+ <br />
+
+
+ <table align="center" style="text-align: center; width: 600px;"
+ border="0" cellspacing="2" cellpadding="2">
+ <tbody>
+ <tr>
+ <td style="vertical-align: top;"><font size="1">* You can find your
+ player id by pressing Menu on your remote control > Online
+ KML Services > Reveal PlayerID.<br />
+ You can use your email address instead of player id only when you
+ have already logged in once and associated your email address with
+ your player id.</font></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><font size="1">** You must have
+ already configured your password by going to the EPG start page
+ > Configure > Set Password</font></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><font size="1">*** Saving player
+ id / email requires cookies</font></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <br />
+ </body>
+ </html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><program-info>IMDbWhen is it on?<title> KRO Filmtheater: Hollywood ending </title><keywords> Film </keywords><description>Komisch filmdrama De regisseur Val Waxman was ooit erg succesvol.
+ Tegenwoordig regisseert hij echter alleen nog maar tv-commercials. Eindelijk krijgt hij weer
+ eens een aanbod om een grote film te maken. Het lot wil echter dat Val op dat moment tijdelijk
+ blind wordt, als resultaat van zijn paranoia. Hij probeert samen met enkele vrienden op de set
+ zijn handicap te verbergen.</description></program-info>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org"/>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1"/>
+ <meta name="generator" content="Adobe GoLive"/>
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css"/>
+ <link rel="shortcut icon" href="../favicon.ico"/>
+ <title>KiSS - Program info</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <img src="../images/KiSS_Logo_small.gif" align="center"/>
+
+ <h1> Program info</h1>
+
+ <table class="tvinfo" align="center" border="0" cellspacing="0" cellpadding="1" width="100%">
+ <tr>
+ <td colspan="3" align="left" bgcolor="black" width="50%">
+ <font size="+1" color="white">
+ <b> KRO Filmtheater: Hollywood ending </b>
+ </font>
+ </td>
+ <td width="10" bgcolor="black"/>
+ <td colspan="3" align="right" bgcolor="black">
+ <font size="+1" color="white">
+ <b> Nederland 1 <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1777728675&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowleft_small.gif" alt="Prev" border="0" align="middle"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1777728677&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowright_small.gif" alt="Next" border="0" align="middle"/>
+ </a> </b>
+ </font>
+ </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td colspan="3" align="center" bgcolor="#B3C5D9" width="50%">
+ <font color="#000000">Sunday 20th August</font>
+ </td>
+ <td width="10"/>
+ <td class="listCell1" colspan="3" align="center" bgcolor="#cccccc">
+ Film </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td align="center" bgcolor="#B3C5D9">
+ <font color="#000000">22:30 - 00:15</font>
+ </td>
+ <td width="10"/>
+ <td class="listCell1" align="center" bgcolor="#cccccc">(01:45 hours)</td>
+ <td width="10"/>
+ <td class="listCell1" colspan="3" align="center">
+ <div align="center"> </div>
+ </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td colspan="7" align="left">Komisch filmdrama De regisseur Val Waxman was ooit erg succesvol.
+ Tegenwoordig regisseert hij echter alleen nog maar tv-commercials. Eindelijk krijgt hij weer
+ eens een aanbod om een grote film te maken. Het lot wil echter dat Val op dat moment tijdelijk
+ blind wordt, als resultaat van zijn paranoia. Hij probeert samen met enkele vrienden op de set
+ zijn handicap te verbergen.</td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td class="listCell1" colspan="7" align="left" valign="middle"> [ <a target="_blank"
+ href="http://www.imdb.com/find?q=KRO+Filmtheater%3A+Hollywood+ending" class="grid"
+ >IMDb</a> ] [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch_show.php?tz=2&tvshow=KRO+Filmtheater%3A+Hollywood+ending&back=$tvhtml$tvinfo.php(tz)2~id)1777728676~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"
+ class="grid">When is it on?</a> ] </td>
+ </tr>
+ </table>
+
+ <table align="center" width="100%" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td><br/>
+ <br/>
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&day=0&page=0&tz=2&progid=0&sel=0&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/back_small.gif" alt="Back" border="0" align="center"/>
+ </a> <a href="tvstart.php">
+ <img src="../images/home_small.gif" alt="Home" border="0" align="center"/>
+ </a> <a href="../logout.php">
+ <img src="../images/logout_small.gif" alt="Logout" border="0" align="center"/>
+ </a> 23:50, Monday 21st August</td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><program-info>
+<<
+>>IMDb<action name="record" reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_add.php?id=1772395857&tz=1&back=$tvhtml$tvinfo.php(station)6~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1" type="recorded">
+
+ </action>When is it on?
+BackHomeLogout<title>Kruispunt</title><keywords>
+ Religieus</keywords><description>
+
+ Achtergronden uit kerk en samenleving.
+
+</description></program-info>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Program info</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1> Program info</h1>
+
+<h2>Kruispunt</h2>
+
+<h2>Nederland 1 <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+<<</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+>></a></h2>
+
+<b>Monday 13th March</b><br />
+ Religieus<br />
+ <b>15:20 - 16:00</b> (40 minutes)<br />
+ <br />
+ Achtergronden uit kerk en samenleving.<br />
+ <br />
+<br />
+[ <a target="_blank"
+href="http://www.imdb.com/find?q=Kruispunt"
+class="grid">IMDb</a> ] [ <img
+src="../images/record_dot.gif" align="middle" hspace="2" /><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_add.php?id=1772395857&tz=1&back=$tvhtml$tvinfo.php(station)6~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Record</a> ] [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch_show.php?tz=1&tvshow=Kruispunt&back=$tvhtml$tvinfo.php(tz)1~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">When is it on?</a> ] <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&day=0&page=0&tz=1&progid=0&sel=0&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:16, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<link rel="STYLESHEET" type="text/css"
+href="http://epg.kml.kiss-technology.com/kiss.css" />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<h1><img
+src="http://epg.kml.kiss-technology.com/images/KiSS_Logo.gif"
+align="middle" />Error</h1>
+
+Show is already in the recording queue!<br />
+<br />
+[ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=6&id=1772583278&back=$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ]<br />
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<link rel="STYLESHEET" type="text/css"
+href="http://epg.kml.kiss-technology.com/kiss.css" />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<h1><img
+src="http://epg.kml.kiss-technology.com/images/KiSS_Logo.gif"
+align="middle" />Error</h1>
+
+This show conflicts with a recording that is already in the <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvinfo.php(station)2~id)1772583335~back)$tvhtml$tvlist.php(channel)2~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)2~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+recording queue</a>!<br />
+<br />
+[ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=2&id=1772583335&back=$tvhtml$tvlist.php(channel)2~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)2~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ]<br />
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - TV Guide - Recordings</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>TV Guide - Recordings</h1>
+
+<h2>Recordings already sent to player</h2>
+
+<b>SBS6</b> - Lois & Clark: The new adventures of Superman -
+ 08:00 - 09:00 -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541086&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <b>VERO</b> - Brainiac -
+<i> 19:40 - 20:10 </i> -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541060&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+<b>VERO</b> - Brainiac - <i> 19:40 - 20:10 </i>
+- <i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541060&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+ <b>DISC</b> - Brainiac -
+<i> 22:00 - 23:00 </i> -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772540925&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+<b>DISC</b> - Brainiac - <i> 22:00 - 23:00 </i>
+- <i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772540925&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+ <b>NL3</b> - The Kumars at no. 42 -
+<i> 15:55 - 16:26 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583485&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+<b>VERO</b> - Stargate SG-1 -
+<i> 18:10 - 18:55 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583562&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <b>VERO</b> - Battlestar Galactica -
+<i> 19:35 - 20:25 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583564&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <br />
+
+<h2>Recordings to be sent to player</h2>
+
+<b>NL1</b> - Samen tegen Kanker -
+<i> 20:30 - 22:25 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583278&from=1&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=6&id=1772583278&back=$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 19:46, Friday 17th March
+</body>
+</html>
+
--- /dev/null
+<report>
+
+ <messages>
+ <message>Hello world!</message>
+ <message>and another message</message>
+ </messages>
+
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent.</description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<!-- dependencies of the kiss crawler itself -->
+
+<target name="kisscrawler.src.d"
+ depends="logging.d,mail.d,commons-email.d,commons-beanutils.d,commons-codec.d,dom4j.d,xerces.d,httpclient.d,jtidy.d,wamblee.support.d,wamblee.crawler.d,spring.d">
+</target>
+
+<target name="kisscrawler.test.d" depends="wamblee.support.test.d,wamblee.crawler.test.d">
+</target>
+
+
+<!-- dependency to use for depending on the kiss crawler -->
+
+<property name="kisscrawler.dist.dir" value="${lib.dir}/wamblee/crawler/kiss"/>
+<target name="wamblee.kisscrawler.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${kisscrawler.dist.dir}">
+ <include name="wamblee-crawler-kiss.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.kisscrawler.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${kisscrawler.dist.dir}">
+ <include name="wamblee-crawler-kiss-test.jar"/>
+ </fileset>
+ </copy>
+</target>
--- /dev/null
+This is the base documentation directory.
+
+skinconf.xml # This file customizes Forrest for your project. In it, you
+ # tell forrest the project name, logo, copyright info, etc
+
+sitemap.xmap # Optional. This sitemap is consulted before all core sitemaps.
+ # See http://forrest.apache.org/docs/project-sitemap.html
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+#=======================================================================
+# CatalogManager.properties for Catalog Entity Resolver.
+#
+# This is the default properties file for your project.
+# This facilitates local configuration of application-specific catalogs.
+# If you have defined any local catalogs, then they will be loaded
+# before Forrest's core catalogs.
+#
+# See the Apache Forrest documentation:
+# http://forrest.apache.org/docs/your-project.html
+# http://forrest.apache.org/docs/validation.html
+
+# verbosity:
+# The level of messages for status/debug (messages go to standard output).
+# The setting here is for your own local catalogs.
+# The verbosity of Forrest's core catalogs is controlled via
+# main/webapp/WEB-INF/cocoon.xconf
+#
+# The following messages are provided ...
+# 0 = none
+# 1 = ? (... not sure yet)
+# 2 = 1+, Loading catalog, Resolved public, Resolved system
+# 3 = 2+, Catalog does not exist, resolvePublic, resolveSystem
+# 10 = 3+, List all catalog entries when loading a catalog
+# (Cocoon also logs the "Resolved public" messages.)
+verbosity=1
+
+# catalogs ... list of additional catalogs to load
+# (Note that Apache Forrest will automatically load its own default catalog
+# from main/webapp/resources/schema/catalog.xcat)
+# Use either full pathnames or relative pathnames.
+# pathname separator is always semi-colon (;) regardless of operating system
+# directory separator is always slash (/) regardless of operating system
+catalogs=../resources/schema/catalog.xcat
+
+# relative-catalogs
+# If false, relative catalog URIs are made absolute with respect to the
+# base URI of the CatalogManager.properties file. This setting only
+# applies to catalog URIs obtained from the catalogs property in the
+# CatalogManager.properties file
+# Example: relative-catalogs=[yes|no]
+relative-catalogs=no
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<html>
+ <head>
+ <title>Raw un-processed HTML page (test1)</title>
+ </head>
+ <body>
+ <h1>raw un-processed HTML page (test1)</h1>
+ <p>
+ This raw HTML page is linked to from xdocs/samples/static.xml
+ and from xdocs/samples/linking.xml
+ </p>
+ <p>All linked-to pages (for example:
+ <a href="test2.html"><a href="test2.html"></a>) are
+ also available.
+ </p>
+ <hr />
+ <p>
+ [return to <a href="index.html">Index</a>]<br>
+ [return to <a href="samples/linking.html">Linking demonstration</a>]
+ </p>
+ </body>
+</html>
--- /dev/null
+<html>
+<head>
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>KiSS crawler report</title>
+</head>
+<body>
+<h1>KiSS crawler report</h1>
+<h2>Successfully recorded programs <p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>18:00 - 18:55: <strong>Stargate SG-1</strong> (Veronica/Serie/soap)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Sf-serie SG-1 krijgt een aanbod van een buitenaardse wereld voor een wondermedicijn.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>19:25 - 20:25: <strong>Stargate SG-1</strong> (Veronica/Serie/soap)</td>
+</tr>
+<tr>
+
+<td>
+<blockquote>
+<font size="-1">Sf-serie Tijdens een vlucht van de nieuwe X-303, codenaam Prometheus, wordt het schip overmand door NID-agenten.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</h2>
+<h2>Conflicts with other recorded programs<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>20:00 - 20:45: <strong>Doctor Who</strong> (BBC1/Drama)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Madame de Pompadour finds the court at Versailles under attack from sinister clockwork killers. Her only hope of salvation lies with the man who has haunted her dreams since childhood - a mysterious stranger known only as the Doctor.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</h2>
+<h2>Possibly interesting programs</h2>
+<p>
+<table cellpadding="5" align="left"></table>
+
+<br clear="left">
+</p>
+<h3>Category: documentaires</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>11:50 - 12:30: <strong>Zembla</strong> (Nederland 3/Documentaire)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1"></font>
+
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>13:10 - 13:35: <strong>Andere tijden</strong> (Nederland 3/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Geschiedenisrubriek met reportages over (bijna) vergeten gebeurtenissen uit de twintigste eeuw. De redactie gaat op zoek naar ooggetuigen en betrokkenen en naar historische filmbeelden om aan de hand daarvan de verhalen van vroeger opnieuw te vertellen.</font>
+</blockquote>
+</td>
+
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: films</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>13:10 - 13:35: <strong>Andere tijden</strong> (Nederland 3/Film)</td>
+</tr>
+<tr>
+<td>
+
+<blockquote>
+<font size="-1">Geschiedenisrubriek met reportages over (bijna) vergeten gebeurtenissen uit de twintigste eeuw. De redactie gaat op zoek naar ooggetuigen en betrokkenen en naar historische filmbeelden om aan de hand daarvan de verhalen van vroeger opnieuw te vertellen.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>22:00 - 06:00: <strong>Face off</strong> (Veronica/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Actiefilm Een FBI-agent wil kost wat het kost een psychotische terrorist pakken, die verantwoordelijk is voor de moord op zijn zoontje. In de strijd om de terrorist in te rekenen raakt deze in een coma. Hij heeft de agent echter nog net kunnen vertellen dat ergens in Los Angeles een bom verborgen ligt. Om op het spoor van deze bom te komen vragen regeringsfunctionarissen of de FBI-agent zich uit wil geven als de moordenaar van zijn zoon. Door middel van een medische ingreep worden de gezichten van beiden verwisseld met alle gevolgen van dien.</font>
+
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>23:25 - 01:00: <strong>Gossip</strong> (Net5/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Thriller Drie studenten doen voor een schoolproject een proef over roddelen. Ze verspreiden een gerucht om te zien hoe lang het duurt voordat deze zich heeft verspreid. Maar wat begint als een onschuldige roddel, escaleert tot een groot misverstand en leidt zelfs tot een arrestatie wegens verkrachting. Het drietal beseft dat hun vooropgezete plan desastreuze gevolgen heeft en dat hun experiment niet meer te stoppen is.</font>
+</blockquote>
+</td>
+
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: science fiction</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>08:00 - 09:00: <strong>Lois & Clark: The new adventures of Superman</strong> (SBS6/Serie/soap)</td>
+</tr>
+
+<tr>
+<td>
+<blockquote>
+<font size="-1">Sf-serie Lex Luthor ontwikkelt verschillende tests om de kracht van Superman te doorgronden.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: wetenschap</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>11:30 - 12:25: <strong>Triumph of life</strong> (RTL4/Documentaire)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Serie documentaires over de evolutie. Al het leven op aarde is ooit ontstaan uit een organisme dat zich door omstandigheden heeft kunnen ontwikkelen tot een wezen dat zich wist voort te planten. Dit ingewikkelde proces voltrok zich miljarden jaren geleden en is sindsdien gaande. Het werd in de negentiende eeuw voor het eerst in kaart gebracht door de Britse bioloog Charles Darwin. Sindsdien wordt de evolutietheorie wetenschappelijk onderzocht en betwijfeld, maar het feit is dat het leven zich in diverse vormen blijft ontwikkelen.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:00 - 00:30: <strong>Sex sense: Bi way</strong> (Discovery Channel/Wetenschap)</td>
+</tr>
+<tr>
+
+<td>
+<blockquote>
+<font size="-1">Documentaireserie Onderzoek naar de wetenschap van seksualiteit, gecombineerd met levendige beelden en ondeugende humor.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:30 - 01:00: <strong>Sex sense: Baring it all</strong> (Discovery Channel/Wetenschap)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+
+<font size="-1">Documentaireserie Onderzoek naar de wetenschap van seksualiteit, gecombineerd met levendige beelden en ondeugende humor.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:40 - 02:10: <strong>Top secret!</strong> (SBS6/Comedy)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Filmkomedie De knappe jaren '50 rock-'n-roll-ster Nick Rivers is in Oost-Duitsland om op te treden. Daar wordt hij verliefd op de dochter van een ontvoerde wetenschapper en komt hij in contact met het Franse verzet.</font>
+
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</body>
+</html>
--- /dev/null
+%PDF-1.3
+%ª«¬
+4 0 obj
+<< /Type /Info
+/Producer (FOP 0.20.4) >>
+endobj
+5 0 obj
+<< /Length 203 /Filter [ /ASCII85Decode /FlateDecode ]
+ >>
+stream
+Gar'!]afWZ&;9q-MRA)RFnblL2&]tQSZsjOOT[ck2SQkp(bfQ[R7ZPq=U24c0dqq_i?B[A.0s\)5f5<IA'lb0eeo`C+`q\Ip/Tke*)7%T+.hT8:QQidXoPLKZM,RXY"bP+;E@%,ZX;V'Aq+M9rH"!g=N5TToDMoqMeUiEe).I_W3q80:jF+;'9bVIeBRb]DhE9:E2be2~>
+endstream
+endobj
+6 0 obj
+<< /Type /Page
+/Parent 1 0 R
+/MediaBox [ 0 0 595 842 ]
+/Resources 3 0 R
+/Contents 5 0 R
+>>
+endobj
+7 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F1
+/BaseFont /Helvetica
+/Encoding /WinAnsiEncoding >>
+endobj
+8 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F5
+/BaseFont /Times-Roman
+/Encoding /WinAnsiEncoding >>
+endobj
+1 0 obj
+<< /Type /Pages
+/Count 1
+/Kids [6 0 R ] >>
+endobj
+2 0 obj
+<< /Type /Catalog
+/Pages 1 0 R
+ >>
+endobj
+3 0 obj
+<<
+/Font << /F1 7 0 R /F5 8 0 R >>
+/ProcSet [ /PDF /ImageC /Text ] >>
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000687 00000 n
+0000000745 00000 n
+0000000795 00000 n
+0000000015 00000 n
+0000000071 00000 n
+0000000365 00000 n
+0000000471 00000 n
+0000000578 00000 n
+trailer
+<<
+/Size 9
+/Root 2 0 R
+/Info 4 0 R
+>>
+startxref
+883
+%%EOF
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+ SVG Anteater logo
+
+To get started with SVG, I'd recommend getting the Adobe SVG plugin, and the
+xml-batik CVS module. Then have a look at the xml-batik/samples files. Use the
+SVG spec (http://www.w3.org/TR/SVG/) as a reference.
+-->
+
+<!-- See Forrest Issue: FOR-229
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg xmlns:for CDATA #FIXED "http://apache.org/forrest">
+ <!ENTITY % textExt "|for:group-name">
+ <!ELEMENT for:group-name (#PCDATA)>
+]>
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xsl:version="1.0"
+ xmlns:for="http://apache.org/forrest"
+ width="220" height="65" >
+ <title>Anteater logo</title>
+
+ <defs>
+
+ <!--
+ <radialGradient id="radialGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </linearGradient>
+ -->
+
+ <linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
+ <stop style="stop-color:white" offset="0"/>
+ <stop style="stop-color:lightgreen" offset="1"/>
+ </linearGradient>
+
+ <filter id="shadowFilter" filterUnits="objectBoundingBox" width="1.4" height="1.4">
+ <!-- Takes the alpha channel (black outline of the text), blurs it and saves as 'blur' -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="2 2" result="blur"/>
+ <!-- Takes saved 'blur' and offsets it by 4 pixels, saves as 'offsetBlur' -->
+ <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <!-- Merges SourceGraphic (original image) and 'offsetBlur', putting the
+ former 'over' the latter, and using the merged result as the finished
+ image -->
+ <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/>
+ </filter>
+
+ </defs>
+
+ <g filter="url(#shadowFilter)" fill="url(#gradient)">
+ <text x="40%" y="60%" style="font-size:24pt; font-family:Verdana ; text-anchor: middle">
+ <for:group-name />
+ </text>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+ SVG Anteater logo
+
+To get started with SVG, I'd recommend getting the Adobe SVG plugin, and the
+xml-batik CVS module. Then have a look at the xml-batik/samples files. Use the
+SVG spec (http://www.w3.org/TR/SVG/) as a reference.
+-->
+
+<!-- See Forrest Issue: FOR-229
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg xmlns:for CDATA #FIXED "http://apache.org/forrest">
+ <!ENTITY % textExt "|for:project-name">
+ <!ELEMENT for:project-name (#PCDATA)>
+]>
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xsl:version="1.0"
+ xmlns:for="http://apache.org/forrest"
+ width="420" height="65" >
+ <title>Anteater logo</title>
+
+ <defs>
+
+ <!--
+ <radialGradient id="radialGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </linearGradient>
+ -->
+
+ <linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
+ <stop style="stop-color:white" offset="0"/>
+ <stop style="stop-color:lightgreen" offset="1"/>
+ </linearGradient>
+
+ <filter id="shadowFilter" filterUnits="objectBoundingBox" width="1.4" height="1.4">
+ <!-- Takes the alpha channel (black outline of the text), blurs it and saves as 'blur' -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="2 2" result="blur"/>
+ <!-- Takes saved 'blur' and offsets it by 4 pixels, saves as 'offsetBlur' -->
+ <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <!-- Merges SourceGraphic (original image) and 'offsetBlur', putting the
+ former 'over' the latter, and using the merged result as the finished
+ image -->
+ <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/>
+ </filter>
+
+ </defs>
+
+ <g filter="url(#shadowFilter)" fill="url(#gradient)">
+ <text x="100%" y="60%" style="font-size:24pt; font-family:Verdana ; text-anchor: end" >
+ <for:project-name />
+ </text>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Automatic Recording for KiSS Hard Disk Recorders</title>
+ </header>
+ <body>
+ <warning>
+ KiSS makes regular updates to their site that sometimes require adaptations
+ to the crawler. If it stops working, check out the most recent version here.
+ </warning>
+ <section id="changelog">
+ <title>Changelog</title>
+
+ <section>
+ <title>31 August 2006</title>
+ <ul>
+ <li>Added windows bat file for running the crawler under windows.
+ Very add-hoc, will be generalized. </li>
+ </ul>
+ </section>
+ <section>
+ <title>24 August 2006</title>
+ <ul>
+ <li>The crawler now uses desktop login for crawling. Also, it is much more efficient since
+ it no longer needs to crawl the individual programs. This is because the channel page
+ includes descriptions of programs in javascript popups which can be used by the crawler.
+ The result is a significant reduction of the load on the KiSS EPG site. Also, the delay
+ between requests has been increased to further reduce load on the KiSS EPG site. </li>
+ <li>
+ The crawler now crawls programs for tomorrow instead of for today.
+ </li>
+ <li>
+ The web based crawler is configured to run only between 7pm and 12pm. It used to run at
+ 5am.
+ </li>
+ </ul>
+ </section>
+
+ <section>
+ <title>13-20 August 2006</title>
+ <p>
+ There were several changes to the login procedure, requiring modifications to the crawler.
+ </p>
+ <ul>
+ <li>The crawler now uses the 'Referer' header field correctly at login.</li>
+ <li>KiSS now uses hidden form fields in their login process which are now also handled correctly by the
+ crawler.</li>
+ </ul>
+ </section>
+ </section>
+ <section id="overview">
+ <title>Overview</title>
+
+ <p>
+ In 2005, <a href="site:links/kiss">KiSS</a> introduced the ability
+ to schedule recordings on KiSS hard disk recorder (such as the
+ DP-558) through a web site on the internet. When a new recording is
+ scheduled through the web site, the KiSS recorder finds out about
+ this new recording by polling a server on the internet.
+ This is a really cool feature since it basically allows programming
+ the recorder when away from home.
+ </p>
+ <p>
+ After using this feature for some time now, I started noticing regular
+ patterns. Often you are looking for the same programs and for certain
+ types of programs. So, wouldn't it be nice to have a program
+ do this work for you and automatically record programs and notify you
+ of possibly interesting ones?
+ </p>
+ <p>
+ This is where the KiSS crawler comes in. This is a simple crawler which
+ logs on to the KiSS electronic programme guide web site and gets
+ programme information from there. Then based on that it automatically
+ records programs for you or sends notifications about interesting ones.
+ </p>
+ <p>
+ In its current version, the crawler can be used in two ways:
+ </p>
+ <ul>
+ <li><strong>standalone program</strong>: A standalone program run as a scheduled task.</li>
+ <li><strong>web application</strong>: A web application running on a java
+ application server. With this type of use, the crawler also features an automatic retry
+ mechanism in case of failures, as well as a simple web interface. </li>
+ </ul>
+ </section>
+
+ <section>
+ <title>Downloading</title>
+
+ <p>
+ At this moment, no formal releases have been made and only the latest
+ version can be downloaded.
+ </p>
+ <p>
+ The easy way to start is the
+ <a href="installs/crawler/kiss/kiss-crawler-bin.zip">standalone program binary version</a>
+ or using the <a href="installs/crawler/kissweb/wamblee-crawler-kissweb.war">web
+ application</a>.
+ </p>
+ <p>
+ The latest source can be obtained from subversion with the
+ URL <code>https://wamblee.org/svn/public/utils</code>. The subversion
+ repository allows read-only access to anyone.
+ </p>
+ <p>
+ The application was developed and tested on SuSE linux 9.1 with JBoss 4.0.2 application
+ server (only required for the web application). It requires at least a Java Virtual Machine
+ 1.5 or greater to run.
+ </p>
+ </section>
+
+ <section>
+ <title>Configuring the crawler</title>
+
+ <p>
+ The crawler comes with three configuration files:
+ </p>
+ <ul>
+ <li><code>crawler.xml</code>: basic crawler configuration
+ tailored to the KiSS electronic programme guide.</li>
+ <li><code>programs.xml</code>: containing a description of which
+ programs must be recorded and which programs are interesting.</li>
+ <li><code>org.wamblee.crawler.properties</code>: Containing a configuration </li>
+ </ul>
+ <p>
+ For the standalone program, all configuration files are in the <code>conf</code> directory.
+ For the web application, the properties files is located in the <code>WEB-INF/classes</code>
+ directory of the web application, and <code>crawler.xml</code> and <code>programs.xml</code>
+ are located outside of the web application at a location configured in the properties file.
+ </p>
+
+
+ <section>
+ <title>Crawler configuration <code>crawler.xml</code></title>
+
+ <p>
+ First of all, copy the <code>config.xml.example</code> file
+ to <code>config.xml</code>. After that, edit the first entry of
+ that file and replace <code>user</code> and <code>passwd</code>
+ with your personal user id and password for the KiSS Electronic
+ Programme Guide.
+ </p>
+ </section>
+
+ <section>
+ <title>Program configuration</title>
+ <p>
+ Interesting TV shows are described using <code>program</code>
+ elements. Each <code>program</code> element contains
+ one or more <code>match</code> elements that describe
+ a condition that the interesting program must match.
+ </p>
+ <p>
+ Matching can be done on the following properties of a program:
+ </p>
+ <table>
+ <tr><th>Field name</th>
+ <th>Description</th></tr>
+ <tr>
+ <td>name</td>
+ <td>Program name</td>
+ </tr>
+ <tr>
+ <td>description</td>
+ <td>Program description</td>
+ </tr>
+ <tr>
+ <td>channel</td>
+ <td>Channel name</td>
+ </tr>
+ <tr>
+ <td>keywords</td>
+ <td>Keywords/classification of the program.</td>
+ </tr>
+ </table>
+ <p>
+ The field to match is specified using the <code>field</code>
+ attribute of the <code>match</code> element. If no field name
+ is specified then the program name is matched. Matching is done
+ by converting the field value to lowercase and then doing a
+ perl-like regular expression match of the provided value. As a
+ result, the content of the match element should be specified in
+ lower case otherwise the pattern will never match.
+ If multiple <code>match</code> elements are specified for a
+ given <code>program</code> element, then all matches must
+ apply for a program to be interesting.
+ </p>
+ <p>
+ Example patterns:
+ </p>
+ <table>
+ <tr>
+ <th>Pattern</th>
+ <th>Example of matching field values</th>
+ </tr>
+ <tr>
+ <td>the.*x.*files</td>
+ <td>"The X files", "The X-Files: the making of"</td>
+ </tr>
+ <tr>
+ <td>star trek</td>
+ <td>"Star Trek Voyager", "Star Trek: The next generation"</td>
+ </tr>
+ </table>
+
+ <p>
+ It is possible that different programs cannot be recorded
+ since they overlap. To deal with such conflicts, it is possible
+ to specify a priority using the <code>priority</code> element.
+ Higher values of the priority value mean a higher priority.
+ If two programs have the same priority, then it is (more or less)
+ unspecified which of the two will be recorded, but it will at least
+ record one program. If no priority is specified, then the
+ priority is 1 (one).
+ </p>
+
+ <p>
+ Since it is not always desirable to try to record every
+ program that matches the criteria, it is also possible to
+ generate notifications for interesting programs only without
+ recording them. This is done by specifying the
+ <code>action</code> alement with the content <code>notify</code>.
+ By default, the <code>action</code> is <code>record</code>.
+ To make the mail reports more readable it is possible to
+ also assign a category to a program for grouping interesting
+ programs. This can be done using the <code>category</code>
+ element. Note that if the <code>action</code> is
+ <code>notify</code>. then the <code>priority</code> element
+ is not used.
+ </p>
+
+ </section>
+
+ <section>
+ <title>Notification configuration</title>
+ <p>
+ Edit the configuration file <code>org.wamblee.crawler.properties</code>.
+ The properties file is self-explanatory.
+ </p>
+ </section>
+ </section>
+
+
+
+
+ <section>
+ <title>Installing and running the crawler</title>
+
+ <section>
+ <title>Standalone application</title>
+ <p>
+ In the binary distribution, execute the
+ <code>run</code> script for your operating system
+ (<code>run.bat</code> for windows, and
+ <code>run.sh</code> for unix).
+ </p>
+ </section>
+
+ <section>
+ <title>Web application</title>
+ <p>
+ After deploying the web application, navigate to the
+ application in your browser (e.g.
+ <code>http://localhost:8080/wamblee-crawler-kissweb</code>).
+ The screen should show an overview of the last time it ran (if
+ it ran before) as well as a button to run the crawler immediately.
+ Also, the result of the last run can be viewed.
+ The crawler will run automatically every morning at 5 AM local time,
+ and will retry at 1 hour intervals in case of failure to retrieve
+ programme information.
+ </p>
+ </section>
+
+ <section>
+ <title>Source distribution</title>
+ <p>
+ With the source code, build everything with
+ <code>ant dist-lite</code>, then locate the binary
+ distribution in <code>lib/wamblee/crawler/kiss/kiss-crawler-bin.zip</code>.
+ Then proceed as for the binary distribution.
+ </p>
+ </section>
+
+ <section>
+ <title>General usage</title>
+ <p>
+ When the crawler runs, it
+ retrieves the programs for tomorrow. As a result, it is advisable
+ to run the program at an early point of the day as a scheduled
+ task (e.g. cron on unix). For the web application this is
+ preconfigured at 5AM.
+ </p>
+ <note>
+ If you deploy the web application today, it will run automatically
+ on the next (!) day. This even holds if you deploy the application
+ before the normal scheduled time.
+ </note>
+
+ <p>
+ Modifying the program to allow it to investigate tomorrow's
+ programs instead is easy as well but not yet implemented.
+ </p>
+ </section>
+
+
+ </section>
+
+ <section id="examples">
+ <title>Examples</title>
+
+ <p>
+ The best example is in the distribution itself. It is my personal
+ <code>programs.xml</code> file.
+ </p>
+ </section>
+
+ <section>
+ <title>Contributing</title>
+
+ <p>
+ You are always welcome to contribute. If you find a problem just
+ tell me about it and if you have ideas am I always interested to
+ hear about them.
+ </p>
+ <p>
+ If you are a programmer and have a fix for a bug, just send me a
+ patch and if you are fanatic enough and have ideas, I can also
+ give you write access to the repository.
+ </p>
+ </section>
+
+
+ </body>
+</document>
--- /dev/null
+
+
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>KiSS Crawler overview page</title>
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="expires" content="0">
+
+ <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
+ <meta http-equiv="description" content="This is my page">
+
+ <!--
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ -->
+ </head>
+
+ <body>
+ <h1>KiSS Crawler Overview</h1>
+
+ <TABLE border="1">
+ <tr>
+
+ <td>
+ Currently running:
+ </td>
+ <td>
+ false
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ Last executed at:
+ </td>
+
+ <td>
+ Sat May 06 05:18:54 CEST 2006
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last result:
+ </td>
+ <td>
+ true
+ </td>
+
+ </tr>
+ <tr>
+ <td>
+ Last message:
+ </td>
+ <td>
+
+ </td>
+ </tr>
+ <tr>
+
+ <td>
+ Last report:
+ </td>
+ <td>
+ <a href="details.html">details</a>
+ </td>
+ </tr>
+
+ </TABLE>
+
+ <FORM action="runnow">
+
+ <INPUT type="submit" name="runnow" value="Run Crawler as soon as possible">
+ </FORM>
+
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Ascii Art sample</title>
+ </header>
+ <body>
+ <section>
+ <title>Sample Ascii Art</title>
+ <p>To create a <code>.png</code> image like the one below with ASCII art, just save
+ the text file with the <code>.aart</code> extension and then link from any page
+ as an image (<code><image src="asci-art-file.png"/></code>).</p>
+ <p><img src="cocoon-pyramid.png" alt="cocoon pyramid of management-(logic-content-style)"/></p>
+ <p>Here is the source file that has created the above image.</p>
+ <source>
+
+ +-------------------+
+ | Management |
+ +-+-------+-------+-+
+ | | |
+ | | |
+ +-------+ +----+----+ +-------+
+ | logic +--+ content +--+ style |
+ +-------+ +---------+ +-------+
+
+ </source>
+ <p>An ascii art pad recognized following ascii characters:</p>
+ <ul>
+ <li> '-' horizontal SVG line</li>
+ <li>'|' vertical SVG line</li>
+ <li> '+' corner</li>
+ <li> \ oblique line</li>
+ <li> String starting with letter, digit, or '_' is converted to a SVG text.</li>
+ </ul>
+ </section>
+ </body>
+ <footer>
+ <legal>Copyright 2002-2004 The Apache Software Foundation or its licensors, as applicable.</legal>
+ </footer>
+</document>
--- /dev/null
+
+ +-------------------+
+ | Management |
+ +-+-------+-------+-+
+ | | |
+ | | |
+ +-------+ +----+----+ +-------+
+ | logic +--+ content +--+ style |
+ +-------+ +---------+ +-------+
+
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE greeting PUBLIC "-//Acme//DTD Hello Document V1.0//EN" "hello-v10.dtd">
+<greeting>
+Hello XML Custom World!!
+</greeting>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Custom Schema</title>
+ </header>
+ <body>
+ <p>Forrest comes with a set of schemas for common documents, however, if you have existing documents
+ that use a different schema you will want to tell Forrest how to work with them. The best way of doing
+ this is to <a href="http://forrest.apache.org/0.7/docs/howto/howto-buildPlugin.html">build a plugin</a>
+ so that you can easily reuse the functionality on different projects. Plugins also allow you to share
+ this new functionality with other users, and to benefit from their contributions to your work.</p>
+
+ <p>If you don't want to build a plugin you can make Forrest process them within your project sitemap
+ (but this won't really save you any work since the process is almost the same). This sample site has
+ a demonstration of using a custom DTD. If you request <a href="custom.html"><a href="custom.html"></a>
+ you can see the results. Take a look at the project <code>sitemap.xmap</code> to see how it is done.</p>
+
+ <note>Adding custom schemas with a plugin has the added benefit of being able to add the schema
+ definition to the catalog file rather than having to reference it directly from within the XML
+ document.</note>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.3//EN" "http://forrest.apache.org/dtd/document-v13.dtd">
+<document>
+ <header>
+ <title>The Apache Forrest xdocs document-v1.3 DTD</title>
+ <notice>The content of this document doesn't make any sense at all.</notice>
+ <abstract>This is a demonstration document using all possible elements in
+ the current Apache Forrest xdocs <code>document-v13.dtd</code>
+ </abstract>
+ </header>
+ <body>
+ <note>
+ This is a demonstration document using all possible elements in the
+ current Apache Forrest xdocs <code>document-v13.dtd</code>
+ (See the <link href="#changes">DTD changes</link> section at the bottom.)
+ </note>
+ <section id="sample">
+ <title>Sample Content</title>
+ <p><strong>Hint:</strong> See the xml source to see how the various
+ elements are used and see the
+<!-- FOR-321 workaround
+ <link href="ext:dtd-docs">DTD reference documentation</link>.
+-->
+ <link href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</link>.
+ </p>
+ <section id="block-inline">
+ <title>Block and inline elements</title>
+ <p>This is a simple paragraph. Most documents contain a fair amount of
+ paragraphs. Paragraphs are called <code><p></code>.</p>
+ <p xml:space="preserve"
+ >With the <code><p xml:space="preserve"></code> attribute, you can declare
+ that whitespace should be preserved, without implying it is in any other
+ way special.</p>
+ <p>
+ This next paragraph has a class attribute of 'quote'. CSS can
+ be used to present this <code><p class='quote'></code> in
+ a different style than the other paragraphs. The handling of
+ this quoted paragraph is defined in the <extra-css>
+ element in the skinconf.xml.
+ </p>
+ <p class="quote">
+ Anyway, like I was sayin', shrimp is the fruit of the sea. You can
+ barbecue it, boil it, broil it, bake it, sautee it. Dey's uh,
+ shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried,
+ stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp,
+ pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and
+ potatoes, shrimp burger, shrimp sandwich. That- that's about it.
+ </p>
+ <p>A number of in-line elements are available in the DTD, we will show them
+ inside an unordered list (<code><ul></code>):</p>
+ <ul>
+ <li>Here is a simple list item (<code><li></code>).</li>
+ <li>Have you seen the use of the <code><code></code> element in the
+ previous item?</li>
+ <li>Also, we have <code><sub></code> and <code><sup></code>
+ elements to show content <sup>above</sup> or <sub>below</sub> the text
+ baseline.</li>
+ <li>There is a facility to <em>emphasize</em> certain words using the
+ <code><em></code> <strong><code><strong></code></strong>
+ elements.</li>
+ <li>We can use
+ <icon height="22" width="26" src="../images/icon.png" alt="feather"/>
+ <code><icon></code>s too.</li>
+ <li>Another possibility is the <code><img></code> element:
+ <img src="../images/icon.png" alt="another feather" height="22" width="26"/>,
+ which offers the ability to refer to an image map.</li>
+ <li>We have elements for hyperlinking:
+ <dl>
+ <dt><code><link href="faq.html"></code></dt>
+ <dd>Use this to
+ <link href="faq.html" title="Example of a document via link">link</link>
+ to another document. As per normal, this will open the new document
+ in the same browser window.</dd>
+
+ <dt><code><link href="#section"></code></dt>
+ <dd>Use this to
+ <link href="#section" title="Example of a document via local anchor">link</link>
+ to the named anchor in the current document.
+ </dd>
+
+ <dt><code><link href="faq.html#forrest"></code></dt>
+ <dd>Use this to
+ <link href="faq.html#forrest" title="Example of a document via link and anchor">link</link>
+ to another document and go to the named anchor. This will open
+ the new document in the same browser window.
+ </dd>
+
+ <dt><code><jump href="faq.html"></code></dt>
+ <dd>Use this to
+ <jump href="faq.html" title="Example of a document via jump">jump</jump>
+ to another document and optionally go to a named
+ <jump href="faq.html#forrest" title="Example of a document via jump to anchor">anchor</jump>
+ within that document. This will open the new document in the same
+ browser window. So what is the difference between link and jump?
+ The jump behaves differently, in that it will replace any frames
+ in the current window.
+ This is the equivalent of
+ <code><a ... target="_top"></code>
+ </dd>
+
+ <dt><code><fork href="faq.html"></code></dt>
+ <dd>Use this to
+ <fork href="faq.html" title="Example of a document via fork">fork</fork>
+ your webbrowser to another document. This will open the document
+ in a new, unnamed browser window.
+ This is the equivalent of
+ <code><a ... target="_blank"></code>
+ </dd>
+ </dl></li>
+
+ <li>Oh, by the way, a definition list <code><dl></code> was used inside
+ the previous list item. We could put another
+ <ul>
+ <li>unordered list</li>
+ <li>inside the list item</li>
+ </ul>
+ <table>
+ <caption>A sample nested table</caption>
+ <tr><td>Or even tables.. </td><td>
+ <table><tr><td>inside tables..</td></tr></table>
+ </td></tr>
+ <tr><td>or inside lists, but I believe this liberty gets quickly quite
+ hairy as you see.</td></tr>
+ </table>
+ </li>
+ </ul>
+ <p>So far for the in-line elements, let's look at some paragraph-level
+ elements.</p>
+ <fixme author="SN">The <code><fixme></code> element is used for stuff
+ which still needs work. Mind the <code>author</code> attribute!</fixme>
+ <note>Use the <code><note></code> element to draw attention to something, e.g. ...The <code><code></code> element is used when the author can't
+ express himself clearly using normal sentences ;-)</note>
+ <warning>Sleep deprivation can be the result of being involved in an open
+ source project. (a.k.a. the <code><warning></code> element).
+ </warning>
+ <note label="Important">If you want your own labels for notes and
+ warnings, specify them using the <code>label</code> attribute.
+ </note>
+ <p>Apart from unordered lists, we have ordered lists too, of course.</p>
+ <ol>
+ <li>Item 1</li>
+ <li>Item 2</li>
+ <li>This should be 3 if my math is still OK.</li>
+ </ol>
+ </section>
+
+ <section id="presentations">
+ <title>Various presentation formats</title>
+
+ <p>This sample document, written in document-v13 XML can be presented
+ via Forrest in a number of different formats. The links in the
+ following list show this document in each of the currently available
+ formats.</p>
+
+ <p>Each of the formats can be made available as a link near the top of
+ the page. Actual placement of those links depends on the skin
+ currently in use. Those links are enabled in the skinconf.xml via the
+ <disable-XXX-link> elements in the skinconf.xml</p>
+
+ <table>
+ <tr>
+ <th>Presentation Format</th>
+
+ <th>Description</th>
+
+ <th>skinconf.xml Element</th>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.html">HTML</link></td>
+
+ <td>This document in HTML format. </td>
+
+ <td>Always generated by default. Cannot be turned off.</td>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.xml">XML</link></td>
+
+ <td>This document in its raw XML format.</td>
+
+ <td><disable-xml-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.pdf">PDF</link></td>
+
+ <td>This document as Adobe PDF</td>
+
+ <td><disable-pdf-link>. By default, set to false, meaning
+ that this link will be shown.</td>
+ </tr>
+
+ <tr>
+ <td>Text</td>
+
+ <td><p>This document as straight text.</p>
+ <p>For additional information see the Forrest text-output
+ plugin.</p></td>
+
+ <td><disable-txt-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td>POD</td>
+
+ <td><p>This document as Perl POD (Plain Old Documentation). Text
+ with minimal formatting directives. If on a *nix system with perl
+ installed, see "man perlpod".</p>
+ <p>For additional information see the Forrest pod-output
+ plugin.</p></td>
+
+ <td><disable-pod-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+ </table>
+ </section>
+ <section id="section">
+ <title>Using sections</title>
+ <p>You can use sections to put some structure in your document. For some
+ strange historical reason, the section title is an attribute of the
+ <code><section></code> element.</p>
+ </section>
+ <section id="sub-section">
+ <title>Sections, the sequel</title>
+ <p>Just some second section.</p>
+ <section id="sub-sub-section">
+ <title>Section 2.1</title>
+ <p>Which contains a subsection (2.1).</p>
+ </section>
+ </section>
+
+ <section id="source">
+ <title>Showing preformatted source code</title>
+ <p>Enough about these sections. Let's have a look at more interesting
+ elements, <code><source></code> for instance:</p>
+ <source>
+// This example is from the book _Java in a Nutshell_ by David Flanagan.
+// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates.
+// You may study, use, modify, and distribute this example for any purpose.
+// This example is provided WITHOUT WARRANTY either expressed or implied.
+
+import java.applet.*; // Don't forget these import statements!
+import java.awt.*;
+
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ // The Graphics class is how you do all drawing in Java.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}</source>
+ <p>CDATA sections are used within
+ <code><source></code> elements so that you can write pointy
+ brackets without needing to escape them with messy
+ <code>&lt;</code> entities ...
+ </p>
+ <source><![CDATA[
+<pointy>
+ easy
+</pointy>
+]]></source>
+ <p>Please take care to still use a sensible line-length within your
+ source elements.</p>
+ </section>
+
+ <section id="table">
+ <title>Using tables</title>
+ <p>And now for a table:</p>
+ <table>
+ <caption>Table caption</caption>
+ <tr>
+ <th>heading cell 1</th>
+ <th>heading cell 2</th>
+ <th>heading cell 3</th>
+ </tr>
+ <tr>
+ <td>data cell</td>
+ <td colspan="2">this data cell spans two columns</td>
+ </tr>
+ <tr>
+ <td>
+ Tables can be nested:
+ </td>
+ <td>
+ <table>
+ <tr>
+ <th>column 1</th>
+ <th>column 2</th>
+ </tr>
+ <tr>
+ <td>cell A</td>
+ <td>cell B</td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <ul><li>and can include most other elements</li><li>such as lists</li></ul>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ <anchor id="second-figure-anchor"/>
+ <section id="figure">
+ <title>Using figures</title>
+ <p>And a <code><figure></code> to end all of this.
+ Note that this can also be implemented with an
+ <code><img></code> element.
+ </p>
+ <figure src="../images/project.png" alt="The fine Forrest logo" width="220" height="65"/>
+ </section>
+ </section>
+
+ <section id="changes">
+ <title>DTD changes</title>
+ <p>See the generated
+<!-- FOR-321 workaround
+ <link href="ext:dtd-docs">DTD reference documentation</link>.
+-->
+ <link href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</link>.
+ </p>
+ <section id="changes-13">
+ <title>Changes since document-v12</title>
+ <p>
+ All v1.2 docs will work fine as v1.3 DTD. The main change is the
+ addition of a @class attribute to every element, which enables the
+ "extra-css" section in the skinconf to be put to good use.
+ </p>
+ </section>
+ <section id="changes-12">
+ <title>Changes since document-v11</title>
+ <p>
+ doc-v12 enhances doc-v11 by relaxing various restrictions that were
+ found to be unnecessary.
+ </p>
+ <ul>
+ <li>
+ Links ((link|jump|fork) and inline elements (br|img|icon|acronym) are
+ allowed inside title.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), table and figure|anchor are
+ allowed inside li.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), lists (ol|ul|dl), table,
+ figure|anchor are allowed inside definition lists (dd) and tables (td
+ and dh).
+ </li>
+ <li>
+ Inline content
+ (strong|em|code|sub|sup|br|img|icon|acronym|link|jump|fork) is
+ allowed in strong and em.
+ </li>
+ </ul>
+ </section>
+ </section>
+ </body>
+ <footer>
+ <legal>This is a legal notice, so it is <strong>important</strong>.</legal>
+ </footer>
+</document>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<html>
+<head>
+ <title>Embedded HTML demonstration page</title>
+</head>
+<body>
+
+<h1><a name="intro" />Embedded HTML demonstration page</h1>
+
+<p>An HTML document is used as the source for this page, and translated
+to the intermediate Apache Forrest xdocs document structure. The sitemap then
+does the normal aggregation with the navigation content and application of
+the skin.
+</p>
+
+<p>
+The html is being interpreted by Forrest and transformed to the
+intermediate Apache xdocs document structure. That stylesheet cannot deal
+with every possibility in unstructured html, so it tries to guess how to
+build <section> elements and such.
+It needs <h1> (<h2> etc.) headings in the source html
+in order to identify sections. Patches are welcome to enhance
+that transformer.
+</p>
+
+<p>
+You can still take advantage of Forrest's
+<a href="http://forrest.apache.org/docs/linking.html">"<b>site:<b>"
+method of linking</a>, for example:
+<a href="site:index"><a href="site:index"></a>
+</p>
+
+<hr>
+<note>XHTML can also be used, but it is just treated as interpreted
+html. Future versions of Forrest will take much more advantage of XHTML.
+</note>
+<hr>
+
+<h1><a name="examples" />Some example uses of HTML</h1>
+<p>
+There are situations when the Apache Forrest xdocs DTD is not sufficient.
+The use of embedded HTML enables you to use HTML code in these situations.
+</p>
+
+<h2><a name="js" />Embedded applets and Javascript</h2>
+
+<p>
+See the
+<a href="javascript:alert('Opened with Javascript via the body of the source html.')">Javascript alert pop-up</a>
+</p>
+
+<h2><a name="forms" />HTML forms for user interaction</h2>
+<p>
+Search the Forrest website via Google:
+<!-- Search Google -->
+<form target="_blank" action="http://www.google.com/search" method="get">
+<input value="forrest.apache.org" name="as_sitesearch" type="hidden">
+<input type=hidden name=ie value=UTF-8>
+<input type=hidden name=oe value=UTF-8>
+<a href="http://www.google.com/">
+<img src="http://www.google.com/logos/Logo_40wht.gif"
+border="0" alt="Google Search" align="middle" width="150" height="55"></a>
+<input type="text" name="as_q" size="25" maxlength="255" value="HTML">
+
+<input type="submit" name="btnG" value="Google Search">
+</form>
+<!-- Search Google -->
+</p>
+
+<p>
+See a demonstration of "html" and "html forms" with our
+<a href="http://forrest.apache.org/mirrors.cgi">Forrest download mirror</a>
+facility and the
+<a href="http://forrest.apache.org/howto/howto-asf-mirror.html">explanation</a> howto document.
+</p>
+
+<h2><a name="invalid" />Invalid HTML</h2>
+<p>
+This paragraph has a missing closing tag for the <p> element. If you look
+at the <a href="embedded_html.xml">XML created by Forrest</a> you'll notice that
+Forrest has fixed this.
+
+<h2>Potentially Invalid XDocs</h2>
+
+<warning>However, it should also be noted that the resultant XML is not a valid document
+since it contains the additional HTML elements. If you are intending to use
+the intermediate XDocs for any purpose be aware of this fact.</warning>
+
+<h2><a name="blink" />Other non-standard html-type abilities</h2>
+<p>
+Use other HTML <blink>delights (???) and tricks</blink>.
+</p>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE faqs PUBLIC "-//APACHE//DTD FAQ V2.0//EN" "http://forrest.apache.org/dtd/faq-v20.dtd">
+
+<faqs>
+ <title>Frequently Asked Questions</title>
+
+ <faqsection id="docs">
+ <title>Documentation</title>
+ <faq id="forrest">
+ <question>
+ How can I help write documentation?
+ </question>
+ <answer>
+ <p>
+ This project uses <a href="ext:forrest">Apache Forrest</a> to
+ generate documentation from XML. Please download a copy of Forrest,
+ which can be used to <a
+ href="ext:forrest/validation">validate</a>, <a
+ href="ext:forrest/webapp">develop</a> and render a project site.
+ </p>
+ </answer>
+ </faq>
+ <!-- More faqs or parts here -->
+ </faqsection>
+ <!-- More faqs or parts here -->
+</faqs>
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!-- ===================================================================
+
+ Apache Hello Document DTD (Version 1.1)
+
+PURPOSE:
+ This DTD was developed to create a sample of custom document.
+
+TYPICAL INVOCATION:
+
+ <!DOCTYPE greeting PUBLIC
+ "-//APACHE//DTD Hello Document Vx.y//EN"
+ "hello-vxy.dtd">
+
+ where
+
+ x := major version
+ y := minor version
+
+NOTES:
+
+FIXME:
+
+CHANGE HISTORY:
+[Version 1.0]
+ 20050112 Initial version. (JJP)
+
+==================================================================== -->
+
+<!ELEMENT greeting (#PCDATA)>
+
+<!-- =============================================================== -->
+<!-- End of DTD -->
+<!-- =============================================================== -->
--- /dev/null
+%PDF-1.3
+%ª«¬
+4 0 obj
+<< /Type /Info
+/Producer (FOP 0.20.4) >>
+endobj
+5 0 obj
+<< /Length 203 /Filter [ /ASCII85Decode /FlateDecode ]
+ >>
+stream
+Gar'!]afWZ&;9q-MRA)RFnblL2&]tQSZsjOOT[ck2SQkp(bfQ[R7ZPq=U24c0dqq_i?B[A.0s\)5f5<IA'lb0eeo`C+`q\Ip/Tke*)7%T+.hT8:QQidXoPLKZM,RXY"bP+;E@%,ZX;V'Aq+M9rH"!g=N5TToDMoqMeUiEe).I_W3q80:jF+;'9bVIeBRb]DhE9:E2be2~>
+endstream
+endobj
+6 0 obj
+<< /Type /Page
+/Parent 1 0 R
+/MediaBox [ 0 0 595 842 ]
+/Resources 3 0 R
+/Contents 5 0 R
+>>
+endobj
+7 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F1
+/BaseFont /Helvetica
+/Encoding /WinAnsiEncoding >>
+endobj
+8 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F5
+/BaseFont /Times-Roman
+/Encoding /WinAnsiEncoding >>
+endobj
+1 0 obj
+<< /Type /Pages
+/Count 1
+/Kids [6 0 R ] >>
+endobj
+2 0 obj
+<< /Type /Catalog
+/Pages 1 0 R
+ >>
+endobj
+3 0 obj
+<<
+/Font << /F1 7 0 R /F5 8 0 R >>
+/ProcSet [ /PDF /ImageC /Text ] >>
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000687 00000 n
+0000000745 00000 n
+0000000795 00000 n
+0000000015 00000 n
+0000000071 00000 n
+0000000365 00000 n
+0000000471 00000 n
+0000000578 00000 n
+trailer
+<<
+/Size 9
+/Root 2 0 R
+/Info 4 0 R
+>>
+startxref
+883
+%%EOF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Samples</title>
+ </header>
+ <body>
+ <section id="please-contribute">
+ <title>If something goes wrong..</title>
+ <p>Patches are welcome: <a href="http://forrest.apache.org/docs/faq.html">Forrest FAQ</a></p>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://apache.org/forrest/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Demonstration of linking</title>
+ </header>
+
+ <body>
+ <section id="overview">
+ <title>Overview</title>
+ <p>Forrest has many powerful techniques for linking between documents
+ and for managing the site navigation. This document demonstrates those
+ techniques.
+ The document "<a href="ext:linking">Menus and Linking</a>"
+ has the full details.
+ </p>
+ </section>
+
+ <section id="uri-space">
+ <title>Building and maintaining consistent URI space</title>
+ <p>
+ When Forrest builds your site, it starts from the front page. Like
+ a robot, it traverses all of the links that it finds in the documents
+ and builds the corresponding pages. Any new links are further traversed.
+ </p>
+ <p>
+ Sometimes those links lead to documents that are generated directly
+ from xml source files, sometimes they are generated from other source
+ via an intermediate xml format. Other times the links lead to raw
+ un-processed content.
+ </p>
+ <p>
+ The site navigation configuration file "<code>site.xml</code>" provides
+ a way to manage this URI space. In the future, when documents are
+ re-arranged and renamed, the site.xml configuration will enable this
+ smoothly.
+ </p>
+ </section>
+
+ <section id="resource-space">
+ <title>Mapping the local resource space to the final URI space</title>
+ <p>
+ For both generated and raw (un-processed) files, the top-level of the
+ URI space corresponds to the "<code>content/xdocs/</code>" directory,
+ i.e. the location of the "<code>site.xml</code>" configuration file.
+ </p>
+ <note>
+ In versions prior to 0.7 raw un-processed content was stored in
+ the "<code>content/</code>" directory. In 0.7 onwards, raw
+ un-processed data is stored alongside the xdocs. In addition,
+ in 0.6 and earlier, HTML documents could be stored in the xdocs
+ directory and served without processing. If you
+ you wish to emulate the behaviour of 0.6 and earlier see the
+ next section.
+ </note>
+ <p>
+ A diagram will help.
+ </p>
+ <source><![CDATA[
+The resource space ==============> The final URI space
+------------------ -------------------
+Generated content ...
+ content/xdocs/index.xml index.html
+ content/xdocs/samples/index.xml samples/index.html
+ content/xdocs/samples/faq.xml samples/faq.html
+ content/xdocs/test1.html test1.html
+ content/xdocs/samples/test3.html samples/test3.html
+ content/xdocs/samples/subdir/test4.html samples/subdir/test4.html
+
+Raw un-processed content ...
+ content/xdocs/hello.pdf hello.pdf
+ content/xdocs/hello.sxw hello.sxw
+ content/xdocs/subdir/hello.sxw subdir/hello.sxw
+]]></source>
+
+ <section>
+ <title>How Plugins May Affect The URI Space</title>
+ <p>By using <a href="site:plugins">Forrest Input Plugins</a>
+ you can process some file formats, such as
+ OpenOffice.org documents and produce processed content from them. For example,
+ the file <code>content/xdocs/hello.sxw</code> can be used to produce a
+ skinned version of the document at with the name <code>hello.html</code>.
+ Similarly, you can use <a href="site:plugins">Forrest Output
+ Plugins</a> to create different output formats such as PDF, in this
+ case <code>content/xdocs/hello.sxw</code> can produce
+ <code>hello.pdf</code>.</p>
+
+ <p>However, this does not affect the handling of raw content. That is, you
+ can still retrieve the raw un-processed version with, for example,
+ <code>hello.sxw</code>. If you want to prevent the user retrieving the
+ un-processed version you will have to create matchers that intercept
+ these requests within your project sitemap.</p>
+ </section>
+
+ </section>
+
+ <section id="generated">
+ <title>Basic link to internal generated pages</title>
+ <p>
+ When this type of link is encountered, Forrest will look for a
+ corresponding xml file, relative to this document (i.e. in
+ <code>content/xdocs/samples/</code>).
+ </p>
+ <p>A generated document in the current directory, which corresponds to
+ <code>content/xdocs/samples/sample.html</code> ...
+ </p>
+ <source><![CDATA[<a href="sample.html">]]><a href="sample.html">sample.html</a><![CDATA[</a>]]></source>
+ <p>In a sub-directory, which corresponds to
+ <code>content/xdocs/samples/subdir/index.html</code> ...
+ </p>
+ <source><![CDATA[<a href="subdir/index.html">]]><a href="subdir/index.html">subdir/index.html</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="raw">
+ <title>Basic link to raw un-processed content</title>
+ <p>
+ Raw content files are not intended for any processing, they are just
+ linked to (e.g. pre-prepared PDFs, zip archives).
+ These files are placed alongside your normal content in the
+ "<code>content/xdocs</code>" directory.
+ </p>
+ <p>A raw document in the current directory, which corresponds to
+ <code>content/xdocs/samples/helloAgain.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="helloAgain.pdf">]]><a href="helloAgain.pdf">helloAgain.pdf</a><![CDATA[</a>]]></source>
+ <p>A raw document in a sub-directory, which corresponds to
+ <code>content/xdocs/samples/subdir/hello.zip</code> ...
+ </p>
+ <source><![CDATA[<a href="subdir/hello.zip">]]><a href="subdir/hello.zip">subdir/hello.zip</a><![CDATA[</a>]]></source>
+ <p>A raw document at the next level up, which corresponds to
+ <code>content/hello.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="../hello.pdf">]]><a href="../hello.pdf">../hello.pdf</a><![CDATA[</a>]]></source>
+
+ <section>
+ <title>Serving (X)HTML content without Skinning</title>
+
+ <p>Prior to version 0.7, the raw un-processed content was stored in
+ the "<code>content/</code>" directory. In 0.7 onwards, raw
+ un-processed data is stored alongside the xdocs. In addition
+ in 0.6 and earlier, HTML files could be stored in the xdocs
+ directory and they would be served without further processing.
+ As described above, this is not the case in 0.7 where HTML files
+ are, by default, skinned by Forrest.</p>
+
+ <p>If you
+ you wish to emulate the behaviour of 0.6 and earlier then you
+ must add the following to your project sitemap.</p>
+
+ <source>
+<map:match pattern="**.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{0}">
+ <map:read src="{project:content}/{0}" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ <map:when test="{project:content.xdocs}{0}">
+ <map:read src="{project:content.xdocs}/{0}" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content.xdocs}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ </map:select>
+</map:match>
+ </source>
+
+ <p>The above allows us to create links to un-processed skinned files stored
+ in the <code>{project:content}</code> or <code>{project:content.xdocs}</code>
+ directory. For example:
+ <a href="/test1.html">HTML content</a>. However, it will
+ break the 0.7 behaviour of skinning HTML content. For this reason the old
+ ".ehtml" extension can be used to embed HTML content in a Forrest skinned
+ site </p>
+
+ <p>Note that you can change the matchers above to selectively serve some
+ content as raw un-processed content, whilst still serving other content
+ as skinned documents. For example, the following snippet would allow
+ you to serve the content of an old, deprecated site without processing
+ from Forrest, whilst still allowing all other content to be processed
+ by Forrest in the normal way:</p>
+
+ <source>
+<map:match pattern="old_site/**.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{1}.html">
+ <map:read src="{project:content}/{1}.html" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+</map:match>
+ </source>
+
+ <p>For example, <a href="/old_site/test1.html">HTML content</a>.</p>
+ </section>
+ </section>
+
+ <section id="url">
+ <title>Full URL to external documents</title>
+ <p>A full URL ...</p>
+ <source><![CDATA[<a href="http://forrest.apache.org/">]]><a href="http://forrest.apache.org/">http://forrest.apache.org/</a><![CDATA[</a>]]></source>
+ <p>A full URL with a fragment identifier ...</p>
+ <source><![CDATA[<a href="http://forrest.apache.org/faq.html#link_raw">]]><a href="http://forrest.apache.org/faq.html#link_raw">http://forrest.apache.org/faq.html#link_raw</a><![CDATA[</a>]]></source>
+ <p>
+ Note that Forrest does not traverse external links to look for
+ other links.
+ </p>
+ </section>
+
+ <section id="site">
+ <title>Using site.xml to manage the links</title>
+ <p>As you will have discovered, using pathnames with ../../ etc. will
+ get very nasty. Real problems occur when you use a smart text editor
+ that tries to manage the links for you. For example, it will have
+ trouble linking to the raw content files which are not yet in their
+ final location.
+ </p>
+ <p>
+ Links and filenames are bound to change and re-arrange. It is
+ essential to only change those links in one central place, not in every
+ document.
+ </p>
+ <p>
+ The "<code>site.xml</code>" configuration file to the rescue. It maps
+ symbolic names to actual resources.
+ </p>
+
+ <section id="site-simple">
+ <title>Basic link to internal generated pages</title>
+ <p>This single entry ...</p>
+ <source><![CDATA[<index label="Index" href="index.html"/>]]></source>
+ <p>
+ enables a simple link to a generated document, which corresponds to
+ <code>content/xdocs/index.xml</code> ...
+ </p>
+ <source><![CDATA[<a href="site:index">]]><a href="site:index">site:index</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="site-compound">
+ <title>Group some items</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <samples label="Samples" href="samples/" tab="samples">
+ <faq label="FAQ" href="faq.html"/>
+ ...
+ </samples>
+]]></source>
+ <p>
+ enables a link to a generated document, which corresponds to
+ <code>content/xdocs/samples/index.xml</code> ...
+ </p>
+ <source><![CDATA[<a href="site:samples">]]><a href="site:samples">site:samples</a><![CDATA[</a>]]></source>
+ <p>
+ and a link to a generated document, which corresponds to
+ <code>content/xdocs/samples/faq.xml</code> ...
+ </p>
+ <source>
+<![CDATA[<a href="site:faq">]]><a href="site:faq">site:faq</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="site:samples/faq">]]><a href="site:samples/faq">site:samples/faq</a><![CDATA[</a>]]>
+ </source>
+ </section>
+
+ <section id="site-fragment">
+ <title>Fragment identifiers</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <samples label="Samples" href="samples/" tab="samples">
+ <sample label="Apache document" href="sample.html">
+ <top href="#top"/>
+ <section href="#section"/>
+ </sample>
+ ...
+ </samples>
+]]></source>
+ <p>
+ enables a link to a fragment identifier within the
+ <code>samples/sample.html</code> document ...
+ </p>
+ <source><![CDATA[<a href="site:samples/sample/section">]]><a href="site:samples/sample/section">site:samples/sample/section</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="site-raw">
+ <title>Define items for raw content</title>
+ <p>This entry ...</p>
+ <source><![CDATA[<hello_print href="hello.pdf"/>]]></source>
+ <p>
+ enables a link to a raw document, which corresponds to
+ <code>content/hello.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="site:hello_print">]]><a href="site:hello_print">site:hello_print</a><![CDATA[</a>]]></source>
+
+ </section>
+
+ <section id="site-ext">
+ <title>External links</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <external-refs>
+ <forrest href="http://forrest.apache.org/">
+ <linking href="docs/linking.html"/>
+ <webapp href="docs/your-project.html#webapp"/>
+ </forrest>
+ </external-refs>
+]]></source>
+ <p>
+ enables a link to an external URL ...
+ </p>
+ <source><![CDATA[<a href="ext:forrest">]]><a href="ext:forrest">ext:forrest</a><![CDATA[</a>]]></source>
+ <p>
+ and a link to another external URL ...
+ </p>
+ <source>
+<![CDATA[<a href="ext:linking">]]><a href="ext:linking">ext:linking</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="ext:forrest/linking">]]><a href="ext:forrest/linking">ext:forrest/linking</a><![CDATA[</a>]]>
+ </source>
+ <p>
+ and a link to another external URL with a fragment identifier ...
+ </p>
+ <source>
+<![CDATA[<a href="ext:webapp">]]><a href="ext:webapp">ext:webapp</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="ext:forrest/webapp">]]><a href="ext:forrest/webapp">ext:forrest/webapp</a><![CDATA[</a>]]>
+ </source>
+ </section>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>The Apache Forrest xdocs document-v2.0 DTD</title>
+ <notice>The content of this document doesn't make any sense at all.</notice>
+ <abstract>This is a demonstration document using all possible elements in
+ the current Apache Forrest xdocs <code>document-v20.dtd</code>
+ </abstract>
+ </header>
+ <body>
+ <note>
+ This is a demonstration document using all possible elements in the
+ current Apache Forrest xdocs <code>document-v20.dtd</code>
+ (See the <a href="#changes">DTD changes</a> section at the bottom.)
+ </note>
+ <section id="sample">
+ <title>Sample Content</title>
+ <p><strong>Hint:</strong> See the xml source to see how the various
+ elements are used and see the
+<!-- FOR-321 workaround
+ <a href="ext:dtd-docs">DTD reference documentation</a>.
+-->
+ <a href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</a>.
+ </p>
+ <section id="block-inline">
+ <title>Block and inline elements</title>
+ <p>This is a simple paragraph. Most documents contain a fair amount of
+ paragraphs. Paragraphs are called <code><p></code>.</p>
+ <p xml:space="preserve"
+ >With the <code><p xml:space="preserve"></code> attribute, you can declare
+ that whitespace should be preserved, without implying it is in any other
+ way special.</p>
+ <p>
+ This next paragraph has a class attribute of 'quote'. CSS can
+ be used to present this <code><p class='quote'></code> in
+ a different style than the other paragraphs. The handling of
+ this quoted paragraph is defined in the <extra-css>
+ element in the skinconf.xml.
+ </p>
+ <p class="quote">
+ Anyway, like I was sayin', shrimp is the fruit of the sea. You can
+ barbecue it, boil it, broil it, bake it, sautee it. Dey's uh,
+ shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried,
+ stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp,
+ pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and
+ potatoes, shrimp burger, shrimp sandwich. That- that's about it.
+ </p>
+ <p>A number of in-line elements are available in the DTD, we will show them
+ inside an unordered list (<code><ul></code>):</p>
+ <ul>
+ <li>Here is a simple list item (<code><li></code>).</li>
+ <li>Have you seen the use of the <code><code></code> element in the
+ previous item?</li>
+ <li>Also, we have <code><sub></code> and <code><sup></code>
+ elements to show content <sup>above</sup> or <sub>below</sub> the text
+ baseline.</li>
+ <li>There is a facility to <em>emphasize</em> certain words using the
+ <code><em></code> <strong><code><strong></code></strong>
+ elements.</li>
+ <li>We can use
+ <icon height="22" width="26" src="../images/icon.png" alt="feather"/>
+ <code><icon></code>s too.</li>
+ <li>Another possibility is the <code><img></code> element:
+ <img src="../images/icon.png" alt="another feather" height="22" width="26"/>,
+ which offers the ability to refer to an image map.</li>
+ <li>We have elements for hyperlinking:
+ <dl>
+ <dt><code><a href="faq.html"></code></dt>
+ <dd>Use this to
+ <a href="faq.html" title="Example of a document via link">link</a>
+ to another document. As per normal, this will open the new document
+ in the same browser window.</dd>
+
+ <dt><code><a href="#section"></code></dt>
+ <dd>Use this to
+ <a href="#section" title="Example of a document via local anchor">link</a>
+ to the named anchor in the current document.
+ </dd>
+
+ <dt><code><a href="faq.html#forrest"></code></dt>
+ <dd>Use this to
+ <a href="faq.html#forrest" title="Example of a document via link and anchor">link</a>
+ to another document and go to the named anchor. This will open
+ the new document in the same browser window.
+ </dd>
+ <dt>Targetted window control with jump and fork.</dt>
+ <dd>See demonstration
+ <a href="#link-class">using class attribute on links</a>.
+ </dd>
+ </dl></li>
+
+ <li>Oh, by the way, a definition list <code><dl></code> was used inside
+ the previous list item. We could put another
+ <ul>
+ <li>unordered list</li>
+ <li>inside the list item</li>
+ </ul>
+ <table>
+ <caption>A sample nested table</caption>
+ <tr><td>Or even tables.. </td><td>
+ <table><tr><td>inside tables..</td></tr></table>
+ </td></tr>
+ <tr><td>or inside lists, but I believe this liberty gets quickly quite
+ hairy as you see.</td></tr>
+ </table>
+ </li>
+ </ul>
+ <p>So far for the in-line elements, let's look at some paragraph-level
+ elements.</p>
+ <fixme author="SN">The <code><fixme></code> element is used for stuff
+ which still needs work. Mind the <code>author</code> attribute!</fixme>
+ <note>Use the <code><note></code> element to draw attention to something, e.g. ...The <code><code></code> element is used when the author can't
+ express himself clearly using normal sentences ;-)</note>
+ <warning>Sleep deprivation can be the result of being involved in an open
+ source project. (a.k.a. the <code><warning></code> element).
+ </warning>
+ <note label="Important">If you want your own labels for notes and
+ warnings, specify them using the <code>label</code> attribute.
+ </note>
+ <p>Apart from unordered lists, we have ordered lists too, of course.</p>
+ <ol>
+ <li>Item 1</li>
+ <li>Item 2</li>
+ <li>This should be 3 if my math is still OK.</li>
+ </ol>
+ </section>
+
+ <section id="presentations">
+ <title>Various presentation formats</title>
+
+ <p>This sample document, written in document-v20 XML can be presented
+ via Forrest in a number of different formats. The links in the
+ following list show this document in each of the currently available
+ formats.</p>
+
+ <p>Each of the formats can be made available as a link near the top of
+ the page. Actual placement of those links depends on the skin
+ currently in use. Those links are enabled in the skinconf.xml via the
+ <disable-XXX-link> elements in the skinconf.xml</p>
+
+ <table>
+ <tr>
+ <th>Presentation Format</th>
+
+ <th>Description</th>
+
+ <th>skinconf.xml Element</th>
+ </tr>
+
+ <tr>
+ <td><a href="sample.html">HTML</a></td>
+
+ <td>This document in HTML format. </td>
+
+ <td>Always generated by default. Cannot be turned off.</td>
+ </tr>
+
+ <tr>
+ <td><a href="sample.xml">XML</a></td>
+
+ <td>This document in its raw XML format.</td>
+
+ <td><disable-xml-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td><a href="sample.pdf">PDF</a></td>
+
+ <td>This document as Adobe PDF</td>
+
+ <td><disable-pdf-link>. By default, set to false, meaning
+ that this link will be shown.</td>
+ </tr>
+
+ <tr>
+ <td>Text</td>
+
+ <td><p>This document as straight text.</p>
+ <p>For additional information see the Forrest text-output
+ plugin.</p></td>
+
+ <td><disable-txt-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td>POD</td>
+
+ <td><p>This document as Perl POD (Plain Old Documentation). Text
+ with minimal formatting directives. If on a *nix system with perl
+ installed, see "man perlpod".</p>
+ <p>For additional information see the Forrest pod-output
+ plugin.</p></td>
+
+ <td><disable-pod-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+ </table>
+ </section>
+ <section id="section">
+ <title>Using sections</title>
+ <p>You can use sections to put some structure in your document. For some
+ strange historical reason, the section title is an attribute of the
+ <code><section></code> element.</p>
+ </section>
+ <section id="sub-section">
+ <title>Sections, the sequel</title>
+ <p>Just some second section.</p>
+ <section id="sub-sub-section">
+ <title>Section 2.1</title>
+ <p>Which contains a subsection (2.1).</p>
+ </section>
+ </section>
+
+ <section id="source">
+ <title>Showing preformatted source code</title>
+ <p>Enough about these sections. Let's have a look at more interesting
+ elements, <code><source></code> for instance:</p>
+ <source>
+// This example is from the book _Java in a Nutshell_ by David Flanagan.
+// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates.
+// You may study, use, modify, and distribute this example for any purpose.
+// This example is provided WITHOUT WARRANTY either expressed or implied.
+
+import java.applet.*; // Don't forget these import statements!
+import java.awt.*;
+
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ // The Graphics class is how you do all drawing in Java.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}</source>
+ <p>CDATA sections are used within
+ <code><source></code> elements so that you can write pointy
+ brackets without needing to escape them with messy
+ <code>&lt;</code> entities ...
+ </p>
+ <source><![CDATA[
+<pointy>
+ easy
+</pointy>
+]]></source>
+ <p>Please take care to still use a sensible line-length within your
+ source elements.</p>
+ </section>
+
+ <section id="table">
+ <title>Using tables</title>
+ <p>And now for a table:</p>
+ <table>
+ <caption>Table caption</caption>
+ <tr>
+ <th>heading cell 1</th>
+ <th>heading cell 2</th>
+ <th>heading cell 3</th>
+ </tr>
+ <tr>
+ <td>data cell</td>
+ <td colspan="2">this data cell spans two columns</td>
+ </tr>
+ <tr>
+ <td>
+ Tables can be nested:
+ </td>
+ <td>
+ <table>
+ <tr>
+ <th>column 1</th>
+ <th>column 2</th>
+ </tr>
+ <tr>
+ <td>cell A</td>
+ <td>cell B</td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <ul><li>and can include most other elements</li><li>such as lists</li></ul>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ <anchor id="second-figure-anchor"/>
+ <section id="figure">
+ <title>Using figures</title>
+ <p>And a <code><figure></code> to end all of this.
+ Note that this can also be implemented with an
+ <code><img></code> element.
+ </p>
+ <figure src="../images/project.png" alt="The fine Forrest logo" width="220" height="65"/>
+ </section>
+ <section id="link-class">
+ <title>Using class attribute on links</title>
+
+ <p>The document-v13 had elements <fork> and <jump>. In
+ document-v20, those elements no longer exist but the functionality can
+ be duplicated by using the @class attribute.
+ Even though the opening of separate windows should be under the
+ control of the user, these techniques can still be employed.</p>
+
+ <table>
+ <tr>
+ <th><p>Document V1.3</p></th>
+
+ <th><p>Document V2.0</p></th>
+ </tr>
+
+ <tr>
+ <td><p><fork href="faq.html"></p></td>
+
+ <td><a class="fork" href="faq.html"><a class="fork"
+ href="faq.html"></a></td>
+ </tr>
+
+ <tr>
+ <td><p><jump href="faq.html"></p></td>
+
+ <td><p><a class="jump" href="faq.html"><a class="jump"
+ href="faq.html"></a></p></td>
+ </tr>
+ </table>
+ </section>
+ </section>
+
+ <section id="changes">
+ <title>DTD changes</title>
+ <p>See the generated
+<!-- FOR-321 workaround
+ <a href="ext:dtd-docs">DTD reference documentation</a>.
+-->
+ <a href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</a>.
+ </p>
+ <section id="changes-20">
+ <title>Changes between document-v13 and document-v20</title>
+ <ul>
+ <li>Renamed <strong><link></strong>
+ to <strong><a></strong>
+ </li>
+ <li>Removed <strong><fork></strong>
+ and <strong><jump></strong> in favour of the
+ <strong><a></strong> element. See demonstration
+ <a href="#link-class">using class attribute on links</a>.
+ </li>
+ </ul>
+ </section>
+ <section id="changes-13">
+ <title>Changes between document-v12 and document-v13</title>
+ <p>
+ All v1.2 docs will work fine as v1.3 DTD. The main change is the
+ addition of a @class attribute to every element, which enables the
+ "extra-css" section in the skinconf to be put to good use.
+ </p>
+ </section>
+ <section id="changes-12">
+ <title>Changes between document-v11 and document-v12</title>
+ <p>
+ doc-v12 enhances doc-v11 by relaxing various restrictions that were
+ found to be unnecessary.
+ </p>
+ <ul>
+ <li>
+ Links ((link|jump|fork) and inline elements (br|img|icon|acronym) are
+ allowed inside title.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), table and figure|anchor are
+ allowed inside li.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), lists (ol|ul|dl), table,
+ figure|anchor are allowed inside definition lists (dd) and tables (td
+ and dh).
+ </li>
+ <li>
+ Inline content
+ (strong|em|code|sub|sup|br|img|icon|acronym|link|jump|fork) is
+ allowed in strong and em.
+ </li>
+ </ul>
+ </section>
+ </section>
+ </body>
+ <footer>
+ <legal>This is a legal notice, so it is <strong>important</strong>.</legal>
+ </footer>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Static content - including raw un-processed files and documents</title>
+ </header>
+ <body>
+ <section>
+ <title>Linking to static content</title>
+ <p>
+ You can place some types of raw content into the xdocs directory. For example,
+ you can place a PDF file in <code>src/documentation/content/xdocs</code> and link
+ to it normally,
+ <strong><a href="../hello.pdf"></strong><a href="../hello.pdf">hello.pdf</a><strong></a></strong>
+ However, note that if the file is one that Forrest is able to process, for example
+ an HTML file, these files will be processed accordingly.</p>
+
+ <p>
+ It is also worth noting that files in the xdocs directory will only be copied
+ into your final site if there is a link to them somewhere in the site. See the next
+ section for details of how to include content that is not linked.</p>
+
+ <p>
+ For more information see the
+ <a href="site:linking">Linking demonstration</a>.</p>
+ </section>
+
+ <section>
+ <title>Including Static Content that is Not Linked</title>
+
+ <p>
+ You can include raw HTML, PDFs, plain-text, and other files. In your final site by
+ placing them in the <code>src/documentation/content</code> directory. Files in this
+ directory will be copied over automatically but will not be processed in any way by
+ Forrest, that is they will be linked to as raw files.</p>
+
+ <p>
+ You can also have sub-directories such as
+ <code>src/documentation/content/samples/subdir/</code> which
+ reflects your main
+ <code>xdocs/</code> tree. The raw files will then end up
+ beside your documents.
+ </p>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE book PUBLIC "-//APACHE//DTD Cocoon Documentation Book V1.0//EN" "http://forrest.apache.org/dtd/book-cocoon-v10.dtd">
+
+<!-- Sample book.xml file. If this file is renamed to 'book.xml', it will be
+used to define the menu in this subdirectory, instead of that generated from
+the usual site.xml mechanism. The normal relative and absolute hrefs are also
+available. -->
+
+<book software="MyProj"
+ title="MyProj"
+ copyright="@year@ The Apache Software Foundation"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <menu label="About">
+ <!-- using the normal site.xml linking mechanism -->
+ <menu-item label="Index" href="site:index"/>
+ <!-- using relative URIs with relative path -->
+ <menu-item label="Sample page" href="../sample.html"/>
+ <!-- using relative URIs with absolute path -->
+ <menu-item label="Sample ihtml page" href="/samples/ihtml-sample.html"/>
+ <!-- using the normal site.xml linking mechanism -->
+ <menu-item label="FAQ" href="site:faq"/>
+ <menu-item label="Changes" href="site:changes"/>
+ <menu-item label="Todo" href="site:todo"/>
+ </menu>
+
+ <menu label="Subdir">
+ <menu-item label="index" href="site:subdir/index"/>
+ </menu>
+
+</book>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Page generated from a sub-directory</title>
+
+ <authors>
+ <person name="Joe Bloggs" email="joe@joescompany.org" />
+ </authors>
+ </header>
+
+ <body>
+ <section>
+ <title>A sub-directory</title>
+ <p>This was generated from a sub-directory.</p>
+ <p>When creating new subdirectories, remember that these <em>must</em>
+ be declared in site.xml or each directory must have a book.xml file.
+ </p>
+ </section>
+ </body>
+</document>
+
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Interactive client-side imagemaps - the usemap attribute</title>
+ </header>
+ <body>
+ <section id="demo">
+ <title>Imagemap demo</title>
+ <p>
+ <img src="/images/usemap.gif" usemap="#my-map"
+ alt="usemap demo" width="256" height="256"/>
+ </p>
+ <p>
+ <map name="my-map">
+ <area shape="rect" coords="173,14,240,71"
+ alt="Rectangle" href="ext:forrest"/>
+ <area shape="circle" coords="53,172,28"
+ alt="Circle" href="../index.html"/>
+ <area shape="default" coords="0,0.256,256"
+ alt="Default" href="http://www.apache.org"/>
+ </map>
+ </p>
+ </section>
+ <section id="source">
+ <title>Source code</title>
+ <source><![CDATA[
+ <p>
+ <img src="/images/usemap.gif" usemap="#my-map"
+ alt="usemap demo" width="256" height="256"/>
+ </p>
+ <p>
+ <map name="my-map">
+ <area shape="rect" coords="173,14,240,71"
+ alt="Rectangle" href="ext:forrest"/>
+ <area shape="circle" coords="53,172,28"
+ alt="Circle" href="../index.html"/>
+ <area shape="default" coords="0,0.256,256"
+ alt="Default" href="http://www.apache.org"/>
+ </map>
+ </p>
+]]></source>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+Forrest site.xml
+
+This file contains an outline of the site's information content. It is used to:
+- Generate the website menus (though these can be overridden - see docs)
+- Provide semantic, location-independent aliases for internal 'site:' URIs, eg
+<link href="site:changes"> links to changes.html (or ../changes.html if in
+ subdir).
+- Provide aliases for external URLs in the external-refs section. Eg, <link
+ href="ext:cocoon"> links to http://cocoon.apache.org/
+
+See http://forrest.apache.org/docs/linking.html for more info
+-->
+
+<site label="MyProj" href="" xmlns="http://apache.org/forrest/linkmap/1.0" tab="">
+ <!-- Note: No matter what you configure here, Forrest will always try to load
+ index.html when you request http://yourHost/.
+ 'How can I use a start-up-page other than index.html?' in the FAQs has more
+ information tells you how to change that.
+ -->
+ <about label="About">
+ <index label="Index" href="index.html" description="Welcome to the KiSS crawler"/>
+ </about>
+
+ <!-- samples label="Samples" href="samples/" tab="samples">
+ <index href="index.html"/>
+ <sample label="Apache doc v2.0" href="sample.html"
+ description="A nonsense document using all possible elements in the current document v2.0">
+ <top href="#top"/>
+ <section href="#section"/>
+ </sample>
+ <document-v13 label="Apache doc v1.3" href="document-v13.html"
+ description="A nonsense document using all possible elements in the document v1.3">
+ <top href="#top"/>
+ <section href="#section"/>
+ </document-v13>
+ <static label="Static content" href="static.html"
+ description="Static raw un-processed content" />
+ <linking label="Linking" href="linking.html"
+ description="Linking explained and demonstrated" />
+ <sample-html label="Embedded HTML" href="embedded_html.html"
+ description="Test of Embedded HTML" />
+ <sample-ascii-art label="ascii-art page" href="ascii-art.html"
+ description="Sample Ascii Art page" />
+ <sample-usemap label="usemap" href="usemap.html"
+ description="Client-side imagemap" />
+ <sample-custom label="User Schemas" href="customSchema.html"
+ description="Custom XML Schemas"/>
+ <custom label="Custom File" href="custom.html" description="A custom XML file"/>
+ <faq label="FAQ" href="faq.html" description="Frequently Asked Questions" />
+ <subdir label="Subdir" href="subdir/">
+ <index label="Index" href="index.html"
+ description="Page generated from a sub-directory"/>
+ </subdir>
+ </samples -->
+
+ <!-- plugins label="Plugins" href="pluginDocs/plugins_0_70/" tab="plugins">
+ <index label="Index" href="index.html" description="List of plugins available for Forrest"/>
+ </plugins -->
+
+ <files>
+ <hello_print href="hello.pdf" />
+ <test1 href="test1.html" />
+ </files>
+
+
+
+ <!--
+ The href must be wholesite.html/pdf You can change the labels and node names
+ <all label="All">
+ <whole_site_html label="Whole Site HTML" href="wholesite.html"/>
+ <whole_site_pdf label="Whole Site PDF" href="wholesite.pdf"/>
+ </all>
+ -->
+
+ <links>
+ <kiss href="http://www.kiss-technology.com"/>
+ </links>
+
+ <external-refs>
+ <forrest href="http://forrest.apache.org/">
+ <linking href="docs/linking.html"/>
+ <validation href="docs/validation.html"/>
+ <webapp href="docs/your-project.html#webapp"/>
+ <dtd-docs href="docs/dtd-docs.html"/>
+ </forrest>
+ <cocoon href="http://cocoon.apache.org/"/>
+ <xml.apache.org href="http://xml.apache.org/"/>
+ </external-refs>
+
+</site>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE tabs PUBLIC "-//APACHE//DTD Cocoon Documentation Tab V1.1//EN" "http://forrest.apache.org/dtd/tab-cocoon-v11.dtd">
+
+<tabs software="MyProj"
+ title="MyProj"
+ copyright="Foo"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <!-- The rules for tabs are:
+ @dir will always have '/@indexfile' added.
+ @indexfile gets appended to @dir if the tab is selected. Defaults to 'index.html'
+ @href is not modified unless it is root-relative and obviously specifies a
+ directory (ends in '/'), in which case /index.html will be added
+ If @id's are present, site.xml entries with a matching @tab will be in that tab.
+
+ Tabs can be embedded to a depth of two. The second level of tabs will only
+ be displayed when their parent tab is selected.
+ -->
+
+ <tab id="" label="Home" dir="" indexfile="index.html"/>
+
+
+ <!-- tab id="samples" label="Samples" dir="samples" indexfile="sample.html">
+ <tab id="samples-index" label="Index" dir="samples" indexfile="index.html"/>
+ <tab id="samples-sample2" label="Sample2" dir="samples" indexfile="static.html"/>
+ </tab -->
+ <!-- tab label="Apache XML Projects" href="http://xml.apache.org">
+ <tab label="Forrest" href="http://forrest.apache.org"/>
+ <tab label="Xerces" href="http://xml.apache.org/xerces"/>
+ </tab -->
+ <!-- tab id="plugins" label="Plugins" dir="pluginDocs/plugins_0_70" indexfile="index.html"/ -->
+ <!-- Add new tabs here, eg:
+ <tab label="How-Tos" dir="community/howto/"/>
+ <tab label="XML Site" dir="xml-site/"/>
+ -->
+
+</tabs>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
+"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
+
+<!-- OASIS XML Catalog for Forrest Documents -->
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+ prefer="public">
+
+<!-- Download -->
+<public publicId="-//Acme//DTD Hello Document V1.0//EN"
+ uri="hello-v10.dtd"/>
+
+</catalog>
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!-- ===================================================================
+
+ Apache Hello DTD (Version 1.0)
+
+PURPOSE:
+
+
+TYPICAL INVOCATION:
+
+ <!DOCTYPE greeting PUBLIC
+ "-//Acme//DTD Hello Document Vx.y//EN"
+ "hello.dtd">
+
+ where
+
+ x := major version
+ y := minor version
+
+FIXME:
+
+CHANGE HISTORY:
+[Version 1.0]
+ 20041009 Initial version. (JJP)
+
+==================================================================== -->
+
+<!-- =============================================================== -->
+<!-- Greeting type element -->
+<!-- =============================================================== -->
+
+<!ELEMENT greeting (#PCDATA)>
+
+<!-- =============================================================== -->
+<!-- End of DTD -->
+<!-- =============================================================== -->
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--+
+ | Transforms Hello document format.
+ +-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:template match="/">
+ <document>
+ <header>
+ <title>
+ <xsl:value-of select="greeting"/>
+ </title>
+ </header>
+ <body>
+ <xsl:value-of select="greeting"/>
+ </body>
+ </document>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2005 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+
+ <map:components>
+ <map:actions>
+ <map:action logger="sitemap.action.sourcetype" name="sourcetype" src="org.apache.forrest.sourcetype.SourceTypeAction">
+ <sourcetype name="hello-v1.0">
+ <document-declaration public-id="-//Acme//DTD Hello Document V1.0//EN" />
+ </sourcetype>
+ </map:action>
+ </map:actions>
+
+ <map:selectors default="parameter">
+ <map:selector logger="sitemap.selector.parameter" name="parameter" src="org.apache.cocoon.selection.ParameterSelector" />
+ </map:selectors>
+ </map:components>
+
+ <map:resources>
+ <map:resource name="transform-to-document">
+ <map:act type="sourcetype" src="{src}">
+ <map:select type="parameter">
+ <map:parameter name="parameter-selector-test" value="{sourcetype}" />
+
+ <map:when test="hello-v1.0">
+ <map:generate src="{project:content.xdocs}{../../1}.xml" />
+ <map:transform src="{project:resources.stylesheets}/hello2document.xsl" />
+ <map:serialize type="xml-document"/>
+ </map:when>
+ </map:select>
+ </map:act>
+ </map:resource>
+ </map:resources>
+
+ <map:pipelines>
+ <map:pipeline>
+ <map:match pattern="old_site/*.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{1}.html">
+ <map:read src="{project:content}{1}.html" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ </map:select>
+ </map:match>
+
+
+ <map:match pattern="installs/**">
+ <map:read src="../../../lib/wamblee/{1}"/>
+ </map:match>
+
+ <map:match pattern="**.xml">
+ <map:call resource="transform-to-document">
+ <map:parameter name="src" value="{project:content.xdocs}{1}.xml" />
+ </map:call>
+ </map:match>
+ </map:pipeline>
+ </map:pipelines>
+</map:sitemap>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2005 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+Skin configuration file. This file contains details of your project,
+which will be used to configure the chosen Forrest skin.
+-->
+
+<!DOCTYPE skinconfig PUBLIC "-//APACHE//DTD Skin Configuration V0.7-1//EN" "http://forrest.apache.org/dtd/skinconfig-v07-1.dtd">
+<skinconfig>
+ <!-- To enable lucene search add provider="lucene" (default is google).
+ Add box-location="alt" to move the search box to an alternate location
+ (if the skin supports it) and box-location="all" to show it in all
+ available locations on the page. Remove the <search> element to show
+ no search box. @domain will enable sitesearch for the specific domain with google.
+ In other words google will search the @domain for the query string.
+ -->
+ <search name="MyProject" domain="mydomain" provider="google"/>
+
+ <!-- Disable the print link? If enabled, invalid HTML 4.0.1 -->
+ <disable-print-link>true</disable-print-link>
+ <!-- Disable the PDF link? -->
+ <disable-pdf-link>false</disable-pdf-link>
+ <!-- Disable the POD link? -->
+ <disable-pod-link>true</disable-pod-link>
+ <!-- Disable the Text link? FIXME: NOT YET IMPLEMENETED. -->
+ <disable-txt-link>true</disable-txt-link>
+ <!-- Disable the xml source link? -->
+ <!-- The xml source link makes it possible to access the xml rendition
+ of the source frim the html page, and to have it generated statically.
+ This can be used to enable other sites and services to reuse the
+ xml format for their uses. Keep this disabled if you don't want other
+ sites to easily reuse your pages.-->
+ <disable-xml-link>true</disable-xml-link>
+
+ <!-- Disable navigation icons on all external links? -->
+ <disable-external-link-image>true</disable-external-link-image>
+
+ <!-- Disable w3c compliance links?
+ Use e.g. align="center" to move the compliance links logos to
+ an alternate location default is left.
+ (if the skin supports it) -->
+ <disable-compliance-links>false</disable-compliance-links>
+
+ <!-- Render mailto: links unrecognisable by spam harvesters? -->
+ <obfuscate-mail-links>true</obfuscate-mail-links>
+ <obfuscate-mail-value>.at.</obfuscate-mail-value>
+
+ <!-- Disable the javascript facility to change the font size -->
+ <disable-font-script>true</disable-font-script>
+
+ <!-- mandatory project logo
+ default skin: renders it at the top -->
+ <project-name>KiSS Crawler</project-name>
+ <project-description>Automatic recording for KiSS harddisk recorders</project-description>
+ <project-url>http://kiss.wamblee.org</project-url>
+ <project-logo>images/project.png</project-logo>
+ <!-- Alternative static image:
+ <project-logo>images/project-logo.gif</project-logo> -->
+
+ <!-- optional group logo
+ default skin: renders it at the top-left corner -->
+ <group-name>wamblee.org</group-name>
+ <group-description></group-description>
+ <group-url>http://wamblee.org</group-url>
+ <group-logo>images/group.png</group-logo>
+ <!-- Alternative static image:
+ <group-logo>images/group-logo.gif</group-logo> -->
+
+ <!-- optional host logo (e.g. sourceforge logo)
+ default skin: renders it at the bottom-left corner -->
+ <host-url></host-url>
+ <host-logo></host-logo>
+
+ <!-- relative url of a favicon file, normally favicon.ico -->
+ <favicon-url></favicon-url>
+
+ <!-- The following are used to construct a copyright statement -->
+ <year>2006</year>
+ <vendor>wamblee.org</vendor>
+ <!-- The optional copyright-link URL will be used as a link in the
+ copyright statement
+ <copyright-link>http://www.apache.org/licenses/</copyright-link>
+ -->
+
+ <!-- Some skins use this to form a 'breadcrumb trail' of links.
+ Use location="alt" to move the trail to an alternate location
+ (if the skin supports it).
+ Omit the location attribute to display the trail in the default location.
+ Use location="none" to not display the trail (if the skin supports it).
+ For some skins just set the attributes to blank.
+
+ NOTE: If a breadcrumb entry points at a local file the href must
+ be complete, that is it must point to the file itself, not to a
+ directory.
+ -->
+ <trail>
+ <link1 name="wamblee.org" href="http://wamblee.org/"/>
+ <link2 name="utils" href="http://wamblee.org/utils"/>
+ <link3 name="KiSS crawler" href="http://kiss.wamblee.org"/>
+ </trail>
+
+ <!-- Configure the TOC, i.e. the Table of Contents.
+ @max-depth
+ how many "section" levels need to be included in the
+ generated Table of Contents (TOC).
+ @min-sections
+ Minimum required to create a TOC.
+ @location ("page","menu","page,menu", "none")
+ Where to show the TOC.
+ -->
+ <toc max-depth="2" min-sections="1" location="page"/>
+
+ <!-- Heading types can be clean|underlined|boxed -->
+ <headings type="boxed"/>
+
+ <!-- The optional feedback element will be used to construct a
+ feedback link in the footer with the page pathname appended:
+ <a href="@href">{@to}</a>
+ -->
+ <feedback to="erik@wamblee.org"
+ href="mailto:erik@wamblee.org?subject=Feedback " >
+ Send feedback about the website to:
+ </feedback>
+
+ <!-- Optional message of the day (MOTD).
+ Note: This is only implemented in the pelt skin.
+ If the optional <motd> element is used, then messages will be appended
+ depending on the URI string pattern.
+ motd-option : Specifies a pattern to match and provides small text content.
+ motd-title : This text will be added in brackets after the <html><title>
+ motd-page : This text will be added in a panel on the face of the page,
+ with the "motd-page-url" being the hyperlink "More".
+ Values for the "location" attribute are:
+ page : on the face of the page, e.g. in the spare space of the toc
+ alt : at the bottom of the left-hand navigation panel
+ both : both
+ -->
+<!--
+ <motd>
+ <motd-option pattern="docs_0_80">
+ <motd-title>v0.8-dev</motd-title>
+ <motd-page location="both">
+ This is documentation for development version v0.8
+ </motd-page>
+ <motd-page-url>/versions/index.html</motd-page-url>
+ </motd-option>
+ <motd-option pattern="docs_0_70">
+ <motd-title>v0.7</motd-title>
+ <motd-page location="both">
+ This is documentation for current version v0.7
+ </motd-page>
+ <motd-page-url>/versions/index.html</motd-page-url>
+ </motd-option>
+ </motd>
+-->
+
+ <!--
+ extra-css - here you can define custom css-elements that are
+ A) overriding the fallback elements or
+ B) adding the css definition from new elements that you may have
+ used in your documentation.
+ -->
+ <extra-css>
+ <!--Example of reason B:
+ To define the css definition of a new element that you may have used
+ in the class attribute of a <p> node.
+ e.g. <p class="quote"/>
+ -->
+ p.quote {
+ margin-left: 2em;
+ padding: .5em;
+ background-color: #f0f0f0;
+ font-family: monospace;
+ }
+ <!--Example:
+ To override the colours of links only in the footer.
+ -->
+ #footer a { color: #0F3660; }
+ #footer a:visited { color: #009999; }
+ </extra-css>
+
+ <colors>
+ <!-- These values are used for the generated CSS files.
+ They essentially "override" the default colors defined in the chosen skin.
+ There are four duplicate "groups" of colors below, denoted by comments:
+ Color group: Forrest, Krysalis, Collabnet, and Lenya using Pelt.
+ They are provided for example only. To customize the colors of any skin,
+ uncomment one of these groups of color elements and change the values
+ of the particular color elements that you wish to change.
+ Note that by default, all color groups are commented-out which means that
+ the default colors provided by the skin are being used.
+ -->
+
+ <!-- Color group: Forrest: example colors similar to forrest.apache.org
+ Some of the element names are obscure, so comments are added to show how
+ the "pelt" skin uses them, other skins might use these elements in a different way.
+ Tip: temporarily change the value of an element to red (#ff0000) and see the effect.
+ pelt: breadtrail: the strip at the top of the page and the second strip under the tabs
+ pelt: header: top strip containing project and group logos
+ pelt: heading|subheading: section headings within the content
+ pelt: navstrip: the strip under the tabs which contains the published date
+ pelt: menu: the left-hand navigation panel
+ pelt: toolbox: the selected menu item
+ pelt: searchbox: the background of the searchbox
+ pelt: border: line border around selected menu item
+ pelt: body: any remaining parts, e.g. the bottom of the page
+ pelt: footer: the second from bottom strip containing credit logos and published date
+ pelt: feedback: the optional bottom strip containing feedback link
+ -->
+<!--
+ <color name="breadtrail" value="#cedfef" font="#0F3660" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="header" value="#294563"/>
+ <color name="tab-selected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="tab-unselected" value="#b5c7e7" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="subtab-selected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="subtab-unselected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="heading" value="#294563"/>
+ <color name="subheading" value="#4a6d8c"/>
+ <color name="published" value="#4C6C8F" font="#FFFFFF"/>
+ <color name="feedback" value="#4C6C8F" font="#FFFFFF" align="center"/>
+ <color name="navstrip" value="#4a6d8c" font="#ffffff" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="menu" value="#4a6d8c" font="#cedfef" link="#ffffff" vlink="#ffffff" hlink="#ffcf00"/>
+ <color name="toolbox" value="#4a6d8c"/>
+ <color name="border" value="#294563"/>
+ <color name="dialog" value="#4a6d8c"/>
+ <color name="searchbox" value="#4a6d8c" font="#000000"/>
+ <color name="body" value="#ffffff" link="#0F3660" vlink="#009999" hlink="#000066"/>
+ <color name="table" value="#7099C5"/>
+ <color name="table-cell" value="#f0f0ff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#CFDCED"/>
+ <color name="footer" value="#cedfef"/>
+-->
+
+ <!-- Color group: Krysalis -->
+<!--
+ <color name="header" value="#FFFFFF"/>
+
+ <color name="tab-selected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="tab-unselected" value="#F7F7F7" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-selected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+
+ <color name="heading" value="#a5b6c6"/>
+ <color name="subheading" value="#CFDCED"/>
+
+ <color name="navstrip" value="#CFDCED" font="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="toolbox" value="#a5b6c6"/>
+ <color name="border" value="#a5b6c6"/>
+
+ <color name="menu" value="#F7F7F7" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="dialog" value="#F7F7F7"/>
+
+ <color name="body" value="#ffffff" link="#0F3660" vlink="#009999" hlink="#000066"/>
+
+ <color name="table" value="#a5b6c6"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#a5b6c6"/>
+
+ <color name="footer" value="#a5b6c6"/>
+-->
+
+ <!-- Color group: Collabnet -->
+<!--
+ <color name="header" value="#003366"/>
+
+ <color name="tab-selected" value="#dddddd" link="#555555" vlink="#555555" hlink="#555555"/>
+ <color name="tab-unselected" value="#999999" link="#ffffff" vlink="#ffffff" hlink="#ffffff"/>
+ <color name="subtab-selected" value="#cccccc" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#cccccc" link="#555555" vlink="#555555" hlink="#555555"/>
+
+ <color name="heading" value="#003366"/>
+ <color name="subheading" value="#888888"/>
+
+ <color name="navstrip" value="#dddddd" font="#555555"/>
+ <color name="toolbox" value="#dddddd" font="#555555"/>
+ <color name="border" value="#999999"/>
+
+ <color name="menu" value="#ffffff"/>
+ <color name="dialog" value="#eeeeee"/>
+
+ <color name="body" value="#ffffff"/>
+
+ <color name="table" value="#ccc"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#003366"/>
+
+ <color name="footer" value="#ffffff"/>
+-->
+ <!-- Color group: Lenya using pelt-->
+<!--
+
+ <color name="header" value="#ffffff"/>
+
+ <color name="tab-selected" value="#E5E4D9" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="tab-unselected" value="#F5F4E9" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-selected" value="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#E5E4D9" link="#000000" vlink="#000000" hlink="#000000"/>
+
+ <color name="heading" value="#E5E4D9"/>
+ <color name="subheading" value="#000000"/>
+ <color name="published" value="#000000"/>
+ <color name="navstrip" value="#E5E4D9" font="#000000"/>
+ <color name="toolbox" value="#CFDCED" font="#000000"/>
+ <color name="border" value="#999999"/>
+
+ <color name="menu" value="#E5E4D9" font="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="dialog" value="#CFDCED"/>
+ <color name="body" value="#ffffff" />
+
+ <color name="table" value="#ccc"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#003366"/>
+
+ <color name="footer" value="#E5E4D9"/>
+-->
+ </colors>
+
+ <!-- Settings specific to PDF output. -->
+ <pdf>
+ <!--
+ Supported page sizes are a0, a1, a2, a3, a4, a5, executive,
+ folio, legal, ledger, letter, quarto, tabloid (default letter).
+ Supported page orientations are portrait, landscape (default
+ portrait).
+ Supported text alignments are left, right, justify (default left).
+ -->
+ <page size="letter" orientation="portrait" text-align="left"/>
+
+ <!--
+ Pattern of the page numbering in the footer - Default is "Page x".
+ first occurrence of '1' digit represents the current page number,
+ second occurrence of '1' digit represents the total page number,
+ anything else is considered as the static part of the numbering pattern.
+ Examples : x is the current page number, y the total page number.
+ <page-numbering-format>none</page-numbering-format> Do not displays the page numbering
+ <page-numbering-format>1</page-numbering-format> Displays "x"
+ <page-numbering-format>p1.</page-numbering-format> Displays "px."
+ <page-numbering-format>Page 1/1</page-numbering-format> Displays "Page x/y"
+ <page-numbering-format>(1-1)</page-numbering-format> Displays "(x-y)"
+ -->
+ <page-numbering-format>Page 1</page-numbering-format>
+
+ <!--
+ Margins can be specified for top, bottom, inner, and outer
+ edges. If double-sided="false", the inner edge is always left
+ and the outer is always right. If double-sided="true", the
+ inner edge will be left on odd pages, right on even pages,
+ the outer edge vice versa.
+ Specified below are the default settings.
+ -->
+ <margins double-sided="false">
+ <top>1in</top>
+ <bottom>1in</bottom>
+ <inner>1.25in</inner>
+ <outer>1in</outer>
+ </margins>
+
+ <!--
+ Print the URL text next to all links going outside the file
+ -->
+ <show-external-urls>false</show-external-urls>
+
+ <!--
+ Disable the copyright footer on each page of the PDF.
+ A footer is composed for each page. By default, a "credit" with role=pdf
+ will be used, as explained below. Otherwise a copyright statement
+ will be generated. This latter can be disabled.
+ -->
+ <disable-copyright-footer>false</disable-copyright-footer>
+ </pdf>
+
+ <!--
+ Credits are typically rendered as a set of small clickable
+ images in the page footer.
+
+ Use box-location="alt" to move the credits to an alternate location
+ (if the skin supports it).
+
+ For example, pelt skin:
+ - box-location="alt" will place the logo at the end of the
+ left-hand coloured menu panel.
+ - box-location="alt2" will place them underneath that panel
+ in the left-hand whitespace.
+ - Otherwise they are placed next to the compatibility icons
+ at the bottom of the screen.
+
+ Comment out the whole <credit>-element if you want no credits in the
+ web pages
+ -->
+ <credits>
+ <credit box-location="alt">
+ <name>Built with Apache Forrest</name>
+ <url>http://forrest.apache.org/</url>
+ <image>images/built-with-forrest-button.png</image>
+ <width>88</width>
+ <height>31</height>
+ </credit>
+ <!-- A credit with @role="pdf" will be used to compose a footer
+ for each page in the PDF, using either "name" or "url" or both.
+ -->
+ <!--
+ <credit role="pdf">
+ <name>Built with Apache Forrest</name>
+ <url>http://forrest.apache.org/</url>
+ </credit>
+ -->
+ </credits>
+
+</skinconfig>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!--
+ This catalog is used so displays how the language name
+ is named by their speakers.
+-->
+<catalogue >
+ <message key="en">English</message>
+ <message key="es">Espanol</message>
+ <message key="it">Italiano</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="en">English</message>
+ <message key="es">Spanish</message>
+ <message key="nl">Dutch</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="en">Inglés</message>
+ <message key="es">Español</message>
+ <message key="nl">Holandés</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="About">About</message>
+ <message key="Index">Index</message>
+ <message key="Changes">Changes</message>
+ <message key="Todo">Todo</message>
+ <message key="Samples">Samples</message>
+ <message key="Apache document">Apache document</message>
+ <message key="Static content">Static content</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki page</message>
+ <message key="ihtml page">Ihtml page</message>
+ <message key="ehtml page">Ehtml page</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">XSP page</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="af">
+ <message key="About">Aangaande</message>
+ <message key="Index">Inhoud</message>
+ <message key="Changes">Veranderinge</message>
+ <message key="Todo">Om te doen</message>
+ <message key="Samples">Voorbeelde</message>
+ <message key="Apache document">Apache dokument</message>
+ <message key="Static content">Statise Inhoud</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki bladsy</message>
+ <message key="ihtml page">Ihtml bladsy</message>
+ <message key="ehtml page">Ehtml bladsy</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Vereenvoudigde Docbook</message>
+ <message key="XSP page">XSP bladsy</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="de">
+ <message key="About">Über</message>
+ <message key="Index">Index</message>
+ <message key="Changes">Änderungen </message>
+ <message key="Todo">Todo</message>
+ <message key="Samples">Beispiele</message>
+ <message key="Apache document">Apache Dokumentationsseite</message>
+ <message key="Static content">Statischer Inhalt</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki Seite</message>
+ <message key="ihtml page">ihtml Seite</message>
+ <message key="ehtml page">ehtml Seite</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Vereinfachte Docbook</message>
+ <message key="XSP page">XSP Seite</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="About">Acerca de</message>
+ <message key="Index">Indice</message>
+ <message key="Changes">Cambios</message>
+ <message key="Todo">Tareas pendientes</message>
+ <message key="Samples">Ejemplos</message>
+ <message key="Apache document">Documento Apache</message>
+ <message key="Static content">Contenido Estático</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Página Wiki</message>
+ <message key="ihtml page">Página ihtml</message>
+ <message key="ehtml page">Página ehtml</message>
+ <message key="FAQ">Preguntas Frecuentes</message>
+ <message key="Simplifed Docbook">Página Simplifed Docbook</message>
+ <message key="XSP page">Página XSP</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="it">
+ <message key="About">Riguardo a</message>
+ <message key="Index">Indice</message>
+ <message key="Changes">Cambiamenti</message>
+ <message key="Todo">Cose da fare</message>
+ <message key="Samples">Esempi</message>
+ <message key="Apache document">Apache document</message>
+ <message key="Static content">Contenuto Statico</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Pagina Wiki</message>
+ <message key="ihtml page">Pagina ihtml</message>
+ <message key="ehtml page">Pagina ehtml</message>
+ <message key="FAQ">Domande frequenti</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">Pagina XSP</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="no">
+ <message key="About">Om</message>
+ <message key="Index">Indeks</message>
+ <message key="Changes">Endringer</message>
+ <message key="Todo">Oppgave liste</message>
+ <message key="Samples">Eksempler</message>
+ <message key="Apache document">Apache Dokument</message>
+ <message key="Static content">Statisk innhold</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki side</message>
+ <message key="ihtml page">ihtml side</message>
+ <message key="ehtml page">ehtml side</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">XSP side</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="ru">
+ <message key="About">О проекте</message>
+ <message key="Index">Содержание</message>
+ <message key="Changes">Изменения</message>
+ <message key="Todo">План</message>
+ <message key="Samples">Примеры</message>
+ <message key="Apache document">Страница документа Apache</message>
+ <message key="Static content">Статическое содержание</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Страница Wiki</message>
+ <message key="ihtml page">Страница ihtml</message>
+ <message key="ehtml page">Страница ehtml</message>
+ <message key="FAQ">Вопросы/Ответы</message>
+ <message key="Simplifed Docbook">Docbook страница</message>
+ <message key="XSP page">XSP страница</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="sk">
+ <message key="About">O programe</message>
+ <message key="Index">Zoznám</message>
+ <message key="Changes">Zmeny</message>
+ <message key="Todo">Úlohy</message>
+ <message key="Samples">Príklady</message>
+ <message key="Apache document">Apache Document</message>
+ <message key="Static content">Statický Obsah</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki stránka</message>
+ <message key="ihtml page">ihtml stránka</message>
+ <message key="ehtml page">ehtml stránka</message>
+ <message key="FAQ">Casté Otázky</message>
+ <message key="Simplifed Docbook">Simplifed Docbook stránka</message>
+ <message key="XSP page">XSP stránka</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="Home">Home</message>
+ <message key="Samples">Samples</message>
+ <message key="Apache XML Projects">Apache XML Projects</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="Home">Inicio</message>
+ <message key="Samples">Ejemplos</message>
+ <message key="Apache XML Projects">Projectos XML Apache</message>
+</catalogue>
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+##############
+# Properties used by forrest.build.xml for building the website
+# These are the defaults, un-comment them only if you need to change them.
+##############
+
+# Prints out a summary of Forrest settings for this project
+#forrest.echo=true
+
+# Project name (used to name .war file)
+#project.name=my-project
+
+# Specifies name of Forrest skin to use
+# See list at http://forrest.apache.org/docs/skins.html
+#project.skin=pelt
+
+# Descriptors for plugins and skins
+# comma separated list, file:// is supported
+#forrest.skins.descriptors=http://forrest.apache.org/skins/skins.xml,file:///c:/myskins/skins.xml
+#forrest.plugins.descriptors=http://forrest.apache.org/plugins/plugins.xml,http://forrest.apache.org/plugins/whiteboard-plugins.xml
+
+##############
+# behavioural properties
+#project.menu-scheme=tab_attributes
+#project.menu-scheme=directories
+
+##############
+# layout properties
+
+# Properties that can be set to override the default locations
+#
+# Parent properties must be set. This usually means uncommenting
+# project.content-dir if any other property using it is uncommented
+
+#project.status=status.xml
+project.content-dir=docs
+#project.raw-content-dir=${project.content-dir}/content
+#project.conf-dir=${project.content-dir}/conf
+#project.sitemap-dir=${project.content-dir}
+#project.xdocs-dir=${project.content-dir}/content/xdocs
+#project.resources-dir=${project.content-dir}/resources
+#project.stylesheets-dir=${project.resources-dir}/stylesheets
+#project.images-dir=${project.resources-dir}/images
+#project.schema-dir=${project.resources-dir}/schema
+#project.skins-dir=${project.content-dir}/skins
+#project.skinconf=${project.content-dir}/skinconf.xml
+#project.lib-dir=${project.content-dir}/lib
+#project.classes-dir=${project.content-dir}/classes
+#project.translations-dir=${project.content-dir}/translations
+
+##############
+# validation properties
+
+# This set of properties determine if validation is performed
+# Values are inherited unless overridden.
+# e.g. if forrest.validate=false then all others are false unless set to true.
+#forrest.validate=true
+#forrest.validate.xdocs=${forrest.validate}
+#forrest.validate.skinconf=${forrest.validate}
+#forrest.validate.sitemap=${forrest.validate}
+#forrest.validate.stylesheets=${forrest.validate}
+#forrest.validate.skins=${forrest.validate}
+#forrest.validate.skins.stylesheets=${forrest.validate.skins}
+
+# *.failonerror=(true|false) - stop when an XML file is invalid
+#forrest.validate.failonerror=true
+
+# *.excludes=(pattern) - comma-separated list of path patterns to not validate
+# e.g.
+#forrest.validate.xdocs.excludes=samples/subdir/**, samples/faq.xml
+#forrest.validate.xdocs.excludes=
+
+
+##############
+# General Forrest properties
+
+# The URL to start crawling from
+#project.start-uri=linkmap.html
+
+# Set logging level for messages printed to the console
+# (DEBUG, INFO, WARN, ERROR, FATAL_ERROR)
+#project.debuglevel=ERROR
+
+# Max memory to allocate to Java
+#forrest.maxmemory=64m
+
+# Any other arguments to pass to the JVM. For example, to run on an X-less
+# server, set to -Djava.awt.headless=true
+#forrest.jvmargs=
+
+# The bugtracking URL - the issue number will be appended
+#project.bugtracking-url=http://issues.apache.org/bugzilla/show_bug.cgi?id=
+#project.bugtracking-url=http://issues.apache.org/jira/browse/
+
+# The issues list as rss
+#project.issues-rss-url=
+
+#I18n Property. Based on the locale request for the browser.
+#If you want to use it for static site then modify the JVM system.language
+# and run once per language
+#project.i18n=true
+
+# The names of plugins that are required to build the project
+# comma separated list (no spaces)
+# You can request a specific version by appending "-VERSION" to the end of
+# the plugin name. If you exclude a version number the latest released version
+# will be used, however, be aware that this may be a development version. In
+# a production environment it is recomended that you specify a known working
+# version.
+# Run "forrest available-plugins" for a list of plug-ins currently available
+project.required.plugins=org.apache.forrest.plugin.output.pdf
+
+# Proxy configuration
+# proxy.host=
+# proxy.port=
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-overview">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <xsl:when test="contains(text(), 'Right now')">
+ <xsl:text>right-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Evening')">
+ <xsl:text>evening</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Afternoon')">
+ <xsl:text>afternoon</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Noon')">
+ <xsl:text>noon</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Morning')">
+ <xsl:text>morning</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Tomorrow')">
+ <xsl:text>tomorrow</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:value-of select="$type"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:value-of select="$type"/>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates
+ select="//xhtml:table[3]//xhtml:tr[xhtml:td[not(contains(@class, 'listCell'))]]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:tr">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[3]//xhtml:a"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="xhtml:td[3]//xhtml:a/@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:apply-templates select=".//xhtml:script"/>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+ <xsl:template match="xhtml:script">
+ <xsl:variable name="script">
+ <xsl:value-of select="."/>
+ </xsl:variable>
+ <xsl:variable name="description">
+ <xsl:value-of
+ select="substring-before(substring-after($script, '<br>'), '"]')"/>
+ </xsl:variable>
+ <description>
+ <xsl:value-of select="$description"/>
+ </description>
+
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates select="//xhtml:tr[xhtml:td[not(contains(@class, 'listCell'))]]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:tr">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[2]//xhtml:a"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="xhtml:td[2]//xhtml:a/@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name=">>" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time> </time></action>
+
+ <action name="Wintertijd" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 23:55 - 00:40 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhalingen" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+00:50 - 06:15 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:45 - 06:59 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:59 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 07:00 - 07:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:10 - 07:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:30 - 07:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:40 - 08:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:00 - 08:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:10 - 08:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 08:30 - 08:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:40 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:00 - 09:10 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:10 - 09:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:30 - 09:55 - </time></action>
+
+ <action name="Schoondochter gezocht" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:55 - 10:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 10:50 - 11:15 - </time></action>
+
+ <action name="Appeltje voor de dorst" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+11:15 - 12:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:00 - 12:10 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:10 - 12:35 - </time></action>
+
+ <action name="Voor alle fans: Drukwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:35 - 12:57 - </time></action>
+
+ <action name="Trekking Lingo" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:57 - 13:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:00 - 13:10 - </time></action>
+
+ <action name="NOS-Sportjournaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:10 - 13:20 - </time></action>
+
+ <action name="Buitenhof" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:20 - 14:15 - </time></action>
+
+ <action name="Hoge bomen in de misdaad" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:15 - 14:55 - </time></action>
+
+ <action name="AVRO Dierenpark" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:55 - 15:20 - </time></action>
+
+ <action name="Kruispunt" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>15:20 - 16:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:00 - 16:05 - </time></action>
+
+ <action name="Helden van nu: Vrijwilligers in de gezondheidszorg" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:05 - 16:30 - </time></action>
+
+ <action name="Leven met verlies" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:30 - 17:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:00 - 17:10 - </time></action>
+
+ <action name="Schepper & co" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:10 - 17:35 - </time></action>
+
+ <action name="MAX & Catherine" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:35 - 18:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:30 - 18:55 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:55 - 19:25 - </time></action>
+
+ <action name="Ingang Oost" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>19:25 - 20:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:00 - 20:30 - </time></action>
+
+ <action name="Netwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:30 - 21:05 - </time></action>
+
+ <action name="Memories" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>21:05 - 22:05 - </time></action>
+
+ <action name="Keyzer & De Boer Advocaten" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:05 - 22:55 - </time></action>
+
+ <action name="NCRV Dokument: Een familie van vaders" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:55 - 23:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>23:50 - 00:20 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:20 - 00:50 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:50 - 06:45 - </time></action>
+
+
+BackHomeLogout</channel-right-now>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a[ count(following::xhtml:a) >= 3]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:value-of select="preceding-sibling::text()[1]"/>
+ </xsl:element>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="favorite-channels">
+ <xsl:apply-templates select="//xhtml:td[contains(@class, 'listCell') and position() = 2]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:td">
+ <action name="{.//xhtml:b}" type="channel-overview"
+ reference="{xhtml:a/@href}"/>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="favorite-channels">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a[not(contains(@href, 'reload')) and text() != 'Back' and text() != 'Home' and text() != 'Logout' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type"><xsl:text>channel-overview</xsl:text></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="crawlerStandalone"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.crawler.properties.xml</value>
+ <value>org.wamblee.crawler.notification.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
+
+ <!-- =====================================================
+ By default, simply copy everything
+ ===================================================== -->
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\r
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"\r
+ version="1.0">\r
+ \r
+ <xsl:output method="xml"/>\r
+ <xsl:strip-space elements="*"/>\r
+ \r
+ <!-- =====================================================\r
+ Copying template.\r
+ ===================================================== -->\r
+ <xsl:template match="@*|node()" mode="copy">\r
+ <xsl:copy>\r
+ <xsl:apply-templates select="@*|node()" mode="copy"/>\r
+ </xsl:copy>\r
+ </xsl:template>\r
+ \r
+ <xsl:template match="/">\r
+ <xsl:element name="login">\r
+ <xsl:apply-templates select="//xhtml:table[@class = 'tvstart']"/>\r
+ </xsl:element>\r
+ </xsl:template>\r
+ \r
+ <xsl:template match="xhtml:table">\r
+ <action type="channels-whats-on-now" name="channels-whats-on-now" reference="{xhtml:tr[3]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="channels-whats-on" name="channels-whats-on" reference="{xhtml:tr[5]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="channels-favorites" name="channels-favorites" reference="{xhtml:tr[7]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="shows-whats-on" name="shows-whats-on" reference="{xhtml:tr[3]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-search" name="shows-search" reference="{xhtml:tr[5]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-favorites" name="shows-favorites" reference="{xhtml:tr[7]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-add-favorite" name="shows-add-favorite" reference="{xhtml:tr[9]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="movies-whats-on" name="movies-whats-on" reference="{xhtml:tr[3]/xhtml:td[5]//@href}"/>\r
+ \r
+ <action type="sports-whats-on" name="sports-whats-on" reference="{xhtml:tr[9]/xhtml:td[5]//@href}"/>\r
+ \r
+ \r
+ </xsl:template>\r
+ \r
+ <xsl:template match="xhtml:a">\r
+ <xsl:variable name="type">\r
+ <xsl:choose>\r
+ <!-- Everything in the Favorite Channels section --> \r
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and \r
+ contains(text(),\r
+ 'Channels')]\r
+ and following::node()[contains(text(), 'Favorite') and\r
+ contains(text(), 'Shows')]">\r
+ <favorite-channels>\r
+ <xsl:choose>\r
+ <xsl:when test="contains(text(), \r
+ 's on now?')">\r
+ <xsl:text>channels-whats-on-now</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 's on?')">\r
+ <xsl:text>channels-whats-on</xsl:text> \r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Favorites')">\r
+ <xsl:text>channels-favorites</xsl:text> \r
+ </xsl:when>\r
+ </xsl:choose>\r
+ </favorite-channels>\r
+ </xsl:when>\r
+ \r
+ <!-- Everything in the Favorite Shows section --> \r
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and \r
+ contains(text(),\r
+ 'Shows')]\r
+ and following::node()[contains(text(), 'Movies')]">\r
+ <xsl:choose>\r
+ <xsl:when test="contains(text(), 's on?')">\r
+ <xsl:text>shows-whats-on</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Search a show')">\r
+ <xsl:text>shows-search</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Favorites')">\r
+ <xsl:text>shows-favorites</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Add a favorite')">\r
+ <xsl:text>shows-add-favorite</xsl:text>\r
+ </xsl:when> \r
+ </xsl:choose> \r
+ </xsl:when>\r
+ \r
+ <!-- The Movies section -->\r
+ <xsl:when test="preceding::node()[contains(text(), 'Movies')]\r
+ and following::node()[contains(text(), 'Sports')] \r
+ and contains(text(), 's on?')">\r
+ <xsl:text>movies-whats-on</xsl:text> \r
+ </xsl:when>\r
+ \r
+ <!-- Everything in the sports section --> \r
+ \r
+ <xsl:when test="preceding::node()[contains(text(), 'Sports')] \r
+ and contains(text(), 's on?')">\r
+ <xsl:text>sports-whats-on</xsl:text> \r
+ </xsl:when>\r
+ \r
+ <xsl:when test="contains(text(), 'Logout')">\r
+ <xsl:text>logout</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Manual')">\r
+ <xsl:text>manual-recording</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'View')">\r
+ <xsl:text>view-recordings</xsl:text>\r
+ </xsl:when>\r
+ <xsl:otherwise>\r
+ <xsl:text>unknown</xsl:text>\r
+ <xsl:value-of select="text()"/>\r
+ </xsl:otherwise>\r
+ </xsl:choose>\r
+ </xsl:variable>\r
+ <xsl:element name="action">\r
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>\r
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>\r
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>\r
+ </xsl:element>\r
+ </xsl:template>\r
+ \r
+</xsl:stylesheet>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="login">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <!-- Everything in the Favorite Channels section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and
+ contains(text(),
+ 'Channels')]
+ and following::node()[contains(text(), 'Favorite') and
+ contains(text(), 'Shows')]">
+
+ <xsl:choose>
+ <xsl:when test="contains(text(),
+ 's on now?')">
+ <xsl:text>channels-whats-on-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 's on?')">
+ <xsl:text>channels-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Favorites')">
+ <xsl:text>channels-favorites</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- Everything in the Favorite Shows section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and
+ contains(text(),
+ 'Shows')]
+ and following::node()[contains(text(), 'Movies')]">
+ <xsl:choose>
+ <xsl:when test="contains(text(), 's on?')">
+ <xsl:text>shows-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Search a show')">
+ <xsl:text>shows-search</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Favorites')">
+ <xsl:text>shows-favorites</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Add a favorite')">
+ <xsl:text>shows-add-favorite</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- The Movies section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Movies')]
+ and following::node()[contains(text(), 'Sports')]
+ and contains(text(), 's on?')">
+ <xsl:text>movies-whats-on</xsl:text>
+ </xsl:when>
+
+ <!-- Everything in the sports section -->
+
+ <xsl:when test="preceding::node()[contains(text(), 'Sports')]
+ and contains(text(), 's on?')">
+ <xsl:text>sports-whats-on</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="contains(text(), 'Logout')">
+ <xsl:text>logout</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Manual')">
+ <xsl:text>manual-recording</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'View')">
+ <xsl:text>view-recordings</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>unknown</xsl:text>
+ <xsl:value-of select="text()"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="login">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 's on now?')">
+ <xsl:text>channels-whats-on-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 's on?')">
+ <xsl:text>channels-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 'Favorites')">
+ <xsl:text>channels-favorites</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 's on?')">
+ <xsl:text>shows-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Search a show')">
+ <xsl:text>shows-search</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Favorites')">
+ <xsl:text>shows-favorites</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Add a favorite')">
+ <xsl:text>shows-add-favorite</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Movies'] and contains(text(), 's on?')">
+ <xsl:text>movies-whats-on</xsl:text>
+ </xsl:when>
+
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Sports'] and contains(text(), 's on?')">
+ <xsl:text>sports-whats-on</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="contains(text(), 'Logout')">
+ <xsl:text>logout</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Manual')">
+ <xsl:text>manual-recording</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'View')">
+ <xsl:text>view-recordings</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ unknown
+ <xsl:value-of select="text()"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="loginform">
+ <xsl:apply-templates select="//xhtml:form"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="//xhtml:form">
+ <xsl:element name="action">
+ <xsl:attribute name="name">login</xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@action"/></xsl:attribute>
+ <xsl:attribute name="type">login</xsl:attribute>
+ <xsl:apply-templates select="//xhtml:input[@type='hidden']"/>
+ </xsl:element>
+
+ </xsl:template>
+
+ <xsl:template match="xhtml:input">
+ <xsl:element name="param">
+ <xsl:attribute name="name">
+ <xsl:value-of select="@name"/>
+ </xsl:attribute>
+ <xsl:attribute name="value">
+ <xsl:value-of select="@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <!-- Mail server configuration -->
+ <bean id="org.wamblee.crawler.kiss.notification.MailServer"
+ class="org.wamblee.crawler.kiss.notification.MailServer">
+ <constructor-arg><value>${org.wamblee.crawler.smtp.host}</value></constructor-arg>
+ <constructor-arg><value type="int">${org.wamblee.crawler.smtp.port}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.smtp.username}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.smtp.password}</value></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.xml.ClasspathUriResolver"
+ class="org.wamblee.xml.ClasspathUriResolver">
+ </bean>
+
+ <bean id="org.wamblee.xml.XslTransformer"
+ class="org.wamblee.xml.XslTransformer">
+ <constructor-arg><ref local="org.wamblee.xml.ClasspathUriResolver"/></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.crawler.kiss.notification.Notifier"
+ class="org.wamblee.crawler.kiss.notification.MailNotifier">
+ <constructor-arg><value>${org.wamblee.crawler.notification.from}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.notification.to}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.notification.subject}</value></constructor-arg>
+ <constructor-arg><value>reportToHtml.xsl</value></constructor-arg>
+ <constructor-arg><value>reportToText.xsl</value></constructor-arg>
+ <constructor-arg><ref local="org.wamblee.crawler.kiss.notification.MailServer"/></constructor-arg>
+ <constructor-arg><ref local="org.wamblee.xml.XslTransformer"/></constructor-arg>
+ </bean>
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="propertyBean"
+ class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+ <property name="locations">
+ <list>
+ <value>org.wamblee.crawler.properties</value>
+ </list>
+ </property>
+ </bean>
+ </beans>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+/**
+ * Abstract visitor of the tv guide with default looping behavior.
+ */
+public abstract class AbstractVisitor implements Visitor {
+
+ /**
+ * Constructs the visitor.
+ *
+ */
+ protected AbstractVisitor() {
+ // Empty
+ }
+
+ /**
+ * Visits the channel by visiting all programs of the channel.
+ *
+ * @param aChannel
+ * Channel to visit.
+ */
+ public void visitChannel(Channel aChannel) {
+ for (Program program : aChannel.getPrograms()) {
+ program.accept(this);
+ }
+ }
+
+ /**
+ * Visits the TV guide by visiting all channels of the guide.
+ *
+ * @param aGuide
+ * TV guide to visit.
+ */
+ public void visitTvGuide(TVGuide aGuide) {
+ for (Channel channel : aGuide.getChannels()) {
+ channel.accept(this);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Represents the programme for a tv channel.
+ */
+public class Channel {
+
+ /**
+ * TV channel name.
+ */
+ private String _name;
+
+ /**
+ * List of programs in chronological order.
+ */
+ private List<Program> _programs;
+
+ /**
+ * Constructs the channel.
+ * @param aName Channel name.
+ * @param aPrograms Programs.
+ */
+ public Channel(String aName, List<Program> aPrograms) {
+ _name = aName;
+ _programs = aPrograms;
+ }
+
+ /**
+ * Gets the channel name.
+ * @return channel name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Gets the list of program.
+ * @return Programs.
+ */
+ public List<Program> getPrograms() {
+ return Collections.unmodifiableList(_programs);
+ }
+
+ /**
+ * Accepts a visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitChannel(this);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+
+/**
+ * Visitor which determines the interesting programs in the TV guide.
+ */
+public class MatchVisitor extends AbstractVisitor {
+
+ /**
+ * Criterion that determines which programs are interesting.
+ */
+ private Condition<Program> _matcher;
+
+ /**
+ * List of interesting programs.
+ */
+ private List<Program> _programs;
+
+ /**
+ * Constructs the visitor.
+ * @param aMatcher Condition describing interesting programs.
+ */
+ public MatchVisitor(Condition<Program> aMatcher) {
+ _matcher = aMatcher;
+ _programs = new ArrayList<Program>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.Visitor#visitProgram(org.wamblee.crawler.kiss.Program)
+ */
+ public void visitProgram(Program aProgram) {
+ if (_matcher.matches(aProgram)) {
+ _programs.add(aProgram);
+ }
+ }
+
+ /**
+ * Gets the list of interesting programs. To be called after applying
+ * the visitor on a tv guide.
+ * @return List of interesting programs.
+ */
+ public List<Program> getMatches() {
+ return _programs;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.io.PrintStream;
+
+
+/**
+ * Print visitor for pretty printing the TV guide.
+ */
+public class PrintVisitor extends AbstractVisitor {
+
+ /**
+ * Stream to print the guide on.
+ */
+ private PrintStream _stream;
+
+ /**
+ * Constructs the print visitor.
+ * @param aStream Stream to print on.
+ */
+ public PrintVisitor(PrintStream aStream) {
+ _stream = aStream;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.Visitor#visitProgram(org.wamblee.crawler.kiss.Program)
+ */
+ public void visitProgram(Program aProgram) {
+ _stream.println(" " + aProgram.toString());
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.AbstractVisitor#visitChannel(org.wamblee.crawler.kiss.Channel)
+ */
+ @Override
+ public void visitChannel(Channel aChannel) {
+ _stream.println(aChannel.getName());
+ super.visitChannel(aChannel);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Comparator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.kiss.main.SystemProperties;
+
+/**
+ * Represents a television program.
+ */
+public class Program {
+
+ private static final String ELEM_PROGRAM = "program";
+
+ private static final String ELEM_NAME = "name";
+
+ private static final String ELEM_KEYWORDS = "keywords";
+
+ private static final String ELEM_DESCRIPTION = "description";
+
+ private static final String ELEM_CHANNEL = "channel";
+
+ private static final String ELEM_INTERVAL = "interval";
+
+ private static final String ELEM_END_TIME = "end";
+
+ private static final String ELEM_BEGIN_TIME = "begin";
+
+ /**
+ * Lexicographical comparison of programs based on (time, title, channel).
+ *
+ */
+ public static class TimeComparator implements Comparator<Program> {
+
+ /**
+ * Lexicographical comparison based on start time, program name, and
+ * channel.
+ *
+ * @param aProgram1
+ * First program.
+ * @param aProgram2
+ * Second program.
+ * @return See {@link Comparator#compare(T, T)}
+ */
+ public int compare(Program aProgram1, Program aProgram2) {
+ int value = aProgram1.getInterval().getBegin().compareTo(
+ aProgram2.getInterval().getBegin());
+ if (value != 0) {
+ return value;
+ }
+ value = aProgram1.getName().compareTo(aProgram2.getName());
+ if (value != 0) {
+ return value;
+ }
+ return aProgram1.getChannel().compareTo(aProgram2.getChannel());
+ }
+ }
+
+ private static final Log LOG = LogFactory.getLog(Program.class);
+
+ /**
+ * Name of the record action on the program details page.
+ */
+ private static final String RECORD_ACTION = "record";
+
+ /**
+ * Result of recording a program.
+ *
+ */
+ public enum RecordingResult {
+ /**
+ * Successfully recorded.
+ */
+ OK,
+
+ /**
+ * Already recorded program.
+ */
+ DUPLICATE,
+
+ /**
+ * Recording conflict with another program.
+ */
+ CONFLICT,
+
+ /**
+ * Program occurred in the past.
+ */
+ OLDSHOW,
+
+ /**
+ * Program could not be recorded for technical reasons.
+ */
+ ERROR;
+ };
+
+ /**
+ * Indent string to use for pretty printing.
+ */
+ private static final String INDENT = " ";
+
+ /**
+ * Channel the program is on.
+ */
+ private String _channel;
+
+ /**
+ * Program name.
+ */
+ private String _name;
+
+ /**
+ * Program description.
+ */
+ private String _description;
+
+ /**
+ * Keywords or classification of the program.
+ */
+ private String _keywords;
+
+ /**
+ * Time interval for the program (from/to).
+ */
+ private TimeInterval _interval;
+
+ /**
+ * Action to execute to obtain program information and/or record the
+ * program.
+ */
+ private Action _programInfo;
+
+ /**
+ * Constructs the program.
+ *
+ * @param aChannel
+ * Channel name.
+ * @param aName
+ * Program name.
+ * @param aDescription
+ * Description.
+ * @param aKeywords
+ * Keywords/classification.
+ * @param aInterval
+ * Time interval.
+ * @param aProgramInfo
+ * Action to execute for detailed program information or for
+ * recording the page.
+ */
+ public Program(String aChannel, String aName, String aDescription,
+ String aKeywords, TimeInterval aInterval, Action aProgramInfo) {
+ _channel = aChannel;
+ _name = aName;
+ _description = aDescription;
+ _keywords = aKeywords;
+ _interval = aInterval;
+ _programInfo = aProgramInfo;
+ }
+
+ /**
+ * Gets the channel.
+ *
+ * @return Channel.
+ */
+ public String getChannel() {
+ return _channel;
+ }
+
+ /**
+ * Gets the program name.
+ *
+ * @return Name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Gets the description.
+ *
+ * @return Description.
+ */
+ public String getDescription() {
+ return _description;
+ }
+
+ /**
+ * Gets the keywords/classification.
+ *
+ * @return Keywords/classification
+ */
+ public String getKeywords() {
+ return _keywords;
+ }
+
+ /**
+ * Gets the time interval.
+ *
+ * @return Time interval.
+ */
+ public TimeInterval getInterval() {
+ return _interval;
+ }
+
+ /**
+ * Checks if recording is possible.
+ *
+ * @return True iff recording is possible.
+ */
+ public boolean isRecordingPossible() {
+ try {
+ return _programInfo.execute().getAction(RECORD_ACTION) != null;
+ } catch (PageException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Records the show.
+ *
+ * @return Status describing the result of recording.
+ */
+ public RecordingResult record() {
+ LOG.info("Recording " + this);
+ if (SystemProperties.isRecordDisabled()) {
+ return RecordingResult.OK;
+ }
+ try {
+ Action record = _programInfo.execute().getAction(RECORD_ACTION);
+ if (record == null) {
+ LOG.info(" result: " + RecordingResult.OLDSHOW);
+ return RecordingResult.OLDSHOW;
+ }
+ Page result = record.execute();
+ RecordingResult recordingResult = RecordingResult.valueOf(result
+ .getContent().getText());
+ LOG.info(" result: " + recordingResult);
+ return recordingResult;
+ } catch (PageException e) {
+ LOG.warn("Technical problem recording program: '" + this + "'", e);
+ LOG.info(" result: " + RecordingResult.ERROR);
+ return RecordingResult.ERROR;
+ }
+ }
+
+ /**
+ * Accepts the visitor.
+ *
+ * @param aVisitor
+ * Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitProgram(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return _interval + " - " + _name + " (" + _channel + "/" + _keywords
+ + ")" + "\n"
+ + (INDENT + _description).replaceAll("\n", "\n" + INDENT);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof Program)) {
+ return false;
+ }
+ Program program = (Program) aObject;
+ return getName().equals(program.getName())
+ && _programInfo.equals(program._programInfo);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ /**
+ * Converts program information to XML.
+ *
+ * @return XML representation of program information.
+ */
+ public Element asXml() {
+ DocumentFactory factory = DocumentFactory.getInstance();
+ Element program = factory.createElement(ELEM_PROGRAM);
+ program.addElement(ELEM_NAME).setText(getName());
+ program.addElement(ELEM_DESCRIPTION).setText(getDescription());
+ program.addElement(ELEM_KEYWORDS).setText(getKeywords());
+ program.addElement(ELEM_CHANNEL).setText(getChannel());
+ Element interval = program.addElement(ELEM_INTERVAL);
+ interval.addElement(ELEM_BEGIN_TIME).setText(
+ getInterval().getBegin().toString());
+ interval.addElement(ELEM_END_TIME).setText(
+ getInterval().getEnd().toString());
+ return program;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * The TV guide.
+ */
+public class TVGuide {
+
+ /**
+ * List of channels.
+ */
+ private List<Channel> _channels;
+
+ /**
+ * Constructs the guide.
+ * @param aChannels Channels of the guide.
+ */
+ public TVGuide(List<Channel> aChannels) {
+ _channels = aChannels;
+ }
+
+ /**
+ * Gets the channels.
+ * @return Channels.
+ */
+ public List<Channel> getChannels() {
+ return Collections.unmodifiableList(_channels);
+ }
+
+ /**
+ * Accepts the visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitTvGuide(this);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * TIme at which a program starts or ends.
+ */
+public class Time implements Comparable {
+
+ /**
+ *
+ */
+ private static final int HOURS_PER_DAY = 24;
+
+ /**
+ *
+ */
+ private static final int EARLY_HOUR = 3;
+
+ /**
+ * Number of seconds per minute.
+ */
+ private static final double SECONDS_PER_MINUTE = 60.0;
+
+ /**
+ * Hour of the time.
+ */
+ private int _hour;
+
+ /**
+ * Minute of the hour.
+ */
+ private int _minute;
+
+ /**
+ * Constructs the time.
+ *
+ * @param aHour
+ * Hour.
+ * @param aMinute
+ * Minute.
+ */
+ public Time(int aHour, int aMinute) {
+ _hour = aHour;
+ _minute = aMinute;
+ }
+
+ /**
+ * Gets the hour.
+ *
+ * @return Hour.
+ */
+ public int getHour() {
+ return _hour;
+ }
+
+ /**
+ * Gets te minute.
+ *
+ * @return Minute.
+ */
+ public int getMinute() {
+ return _minute;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ NumberFormat format = new DecimalFormat("00");
+ return format.format(_hour) + ":" + format.format(_minute);
+ }
+
+ /**
+ * Convert time to floating point value. Useful for comparing two times.
+ *
+ * @return Converted value.
+ */
+ float asFloat() {
+ int hour = _hour;
+ // Hack to make sure that programs appearing shortly after midnight are
+ // sorted
+ // after those running during the day.
+ if (hour <= EARLY_HOUR) {
+ hour += HOURS_PER_DAY;
+ }
+ return (float) hour + (float) _minute / (float) SECONDS_PER_MINUTE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof Time)) {
+ return false;
+ }
+ return toString().equals(aObject.toString());
+ }
+
+ /**
+ * Compares based on time.
+ *
+ * @param aObject
+ * Time object to compare to.
+ * @return See {@link Comparable#compareTo(T)}.
+ */
+ public int compareTo(Object aObject) {
+ if (!(aObject instanceof Time)) {
+ throw new IllegalArgumentException("object not an instance of Time");
+ }
+ Time time = (Time) aObject;
+ return new Float(asFloat()).compareTo(new Float(time.asFloat()));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+/**
+ * Time interval.
+ */
+public class TimeInterval {
+
+ /**
+ * Begin time.
+ */
+ private Time _begin;
+
+ /**
+ * End time.
+ */
+ private Time _end;
+
+ /**
+ * Construts the interval.
+ *
+ * @param aBegin
+ * Start time.
+ * @param aEnd
+ * End time.
+ */
+ public TimeInterval(Time aBegin, Time aEnd) {
+ _begin = aBegin;
+ _end = aEnd;
+ }
+
+ /**
+ * Gets the begin time.
+ *
+ * @return Begin time.
+ */
+ public Time getBegin() {
+ return _begin;
+ }
+
+ /**
+ * Gets the end time.
+ *
+ * @return End time.
+ */
+ public Time getEnd() {
+ return _end;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return _begin + " - " + _end;
+ }
+
+ /**
+ * Determines if there is an overlap between the current interval and given
+ * one.
+ *
+ * @param aInterval
+ * Interval to compare with.
+ * @return True iff there is overlap
+ */
+ public boolean overlap(TimeInterval aInterval) {
+
+ if (isUncertain() || aInterval.isUncertain()) {
+ // Optimistic assume there is no overlap if one of the intervals is
+ // uncertain.
+ return false;
+ }
+
+ if (_end.asFloat() <= aInterval._begin.asFloat()
+ || aInterval._end.asFloat() <= _begin.asFloat()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the actual time that the program corresponds to is
+ * uncertain due to the representation of a period of more than 24 hours
+ * using a 24 hour clock.
+ *
+ * @return True iff the interval is uncertain.
+ */
+ boolean isUncertain() {
+ return _begin.asFloat() > _end.asFloat();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)j
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof TimeInterval)) {
+ return false;
+ }
+ return aObject.toString().equals(aObject.toString());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _begin.hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+
+/**
+ * Visitor of the TV guide.
+ */
+public interface Visitor {
+
+ /**
+ * Visits a program.
+ * @param aProgram Program.
+ */
+ void visitProgram(Program aProgram);
+
+ /**
+ * Visits a channel.
+ * @param aChannel Channel.
+ */
+ void visitChannel(Channel aChannel);
+
+ /**
+ * Visits the guide.
+ * @param aGuide Guide.
+ */
+ void visitTvGuide(TVGuide aGuide);
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package contains the object model for the TV guide and the classes for
+searching the TV guide for relevant programs.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action to execute for an interesting program.
+ */
+public class InterestingProgramAction implements ProgramAction {
+
+ /**
+ * Category under which the interesting program is listed.
+ */
+ private String _category;
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCategory
+ * Category of the program. Useful for structuring the output.
+ */
+ public InterestingProgramAction(String aCategory) {
+ _category = aCategory;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramAction#execute(org.wamblee.crawler.kiss.Program,
+ * org.wamblee.crawler.kiss.Report)
+ */
+ public void execute(Program aProgram, ProgramActionExecutor aReport) {
+ if (aProgram.isRecordingPossible()) {
+ aReport.interestingProgram(_category, aProgram);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.mail.MessagingException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.impl.ConfigurationParser;
+import org.wamblee.crawler.impl.CrawlerImpl;
+import org.wamblee.crawler.kiss.guide.Channel;
+import org.wamblee.crawler.kiss.guide.PrintVisitor;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TVGuide;
+import org.wamblee.crawler.kiss.guide.Time;
+import org.wamblee.crawler.kiss.guide.TimeInterval;
+import org.wamblee.crawler.kiss.notification.NotificationException;
+import org.wamblee.crawler.kiss.notification.Notifier;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.xml.ClasspathUriResolver;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * The KiSS crawler for automatic recording of interesting TV shows.
+ *
+ */
+public class KissCrawler {
+
+ private static final Log LOG = LogFactory.getLog(KissCrawler.class);
+
+ /**
+ * Start URL of the electronic programme guide.
+ */
+ private static final String START_URL = "http://epg.kml.kiss-technology.com/login.php";
+
+ /**
+ * Default socket timeout to use.
+ */
+ private static final int SOCKET_TIMEOUT = 10000;
+
+ /**
+ * Regular expression for matching time interval strings in the retrieved
+ * pages.
+ */
+ private static final String TIME_REGEX = "[^0-9]*([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
+
+ /**
+ * Compiled pattern for the time regular expression.
+ */
+ private Pattern _pattern;
+
+ /**
+ * Runs the KiSS crawler.
+ *
+ * @param aArgs
+ * Arguments, currently all ignored because they are hardcoded.
+ * @throws Exception
+ * In case of problems.
+ */
+ public static void main(String[] aArgs) throws Exception {
+ String crawlerConfig = new File(aArgs[0]).getCanonicalPath();
+ String programConfig = new File(aArgs[1]).getCanonicalPath();
+
+ BeanFactory factory = new StandaloneCrawlerBeanFactory();
+ Notifier notifier = factory.find(Notifier.class);
+ new KissCrawler(START_URL, SOCKET_TIMEOUT, crawlerConfig,
+ programConfig, notifier, new Report());
+ }
+
+ /**
+ * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
+ * EPG guide, filters the guide for interesting programs, tries to record
+ * them, and sends a summary mail to the user.
+ *
+ * @param aCrawlerConfig
+ * Configuration file for the crawler.
+ * @param aProgramConfig
+ * Configuration file describing interesting shows.
+ * @param aNotifier
+ * Object used to send notifications of the results.
+ * @param aReport
+ * Report to use.
+ * @throws IOException
+ * In case of problems reading files.
+ * @throws NotificationException
+ * In case notification fails.
+ * @throws PageException
+ * In case of problems retrieving the TV guide.
+ */
+ public KissCrawler(String aCrawlerConfig, String aProgramConfig,
+ Notifier aNotifier, Report aReport) throws IOException,
+ NotificationException, PageException {
+ this(START_URL, SOCKET_TIMEOUT, aCrawlerConfig, aProgramConfig,
+ aNotifier, aReport);
+ }
+
+ /**
+ * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
+ * EPG guide, filters the guide for interesting programs, tries to record
+ * them, and sends a summary mail to the user.
+ *
+ * @param aStartUrl
+ * Start URL of the electronic programme guide.
+ * @param aSocketTimeout
+ * Socket timeout to use.
+ * @param aCrawlerConfig
+ * Configuration file for the crawler.
+ * @param aProgramConfig
+ * Configuration file describing interesting shows.
+ * @param aNotifier
+ * Object used to send notifications of the results.
+ * @param aReport
+ * Report to use.
+ * @throws IOException
+ * In case of problems reading files.
+ * @throws NotificationException
+ * In case notification fails.
+ * @throws PageException
+ * In case of problems retrieving the TV guide.
+ */
+ public KissCrawler(String aStartUrl, int aSocketTimeout,
+ String aCrawlerConfig, String aProgramConfig, Notifier aNotifier,
+ Report aReport) throws IOException, NotificationException,
+ PageException {
+
+ _pattern = Pattern.compile(TIME_REGEX);
+
+ try {
+ HttpClient client = new HttpClient();
+ // client.getHostConfiguration().setProxy("127.0.0.1", 3128);
+ client.getParams().setParameter("http.socket.timeout",
+ SOCKET_TIMEOUT);
+
+ XslTransformer transformer = new XslTransformer(
+ new ClasspathUriResolver());
+
+ Crawler crawler = createCrawler(aCrawlerConfig, client, transformer);
+ InputStream programConfigFile = new FileInputStream(new File(
+ aProgramConfig));
+ ProgramConfigurationParser parser = new ProgramConfigurationParser();
+ parser.parse(programConfigFile);
+ List<ProgramFilter> programFilters = parser.getFilters();
+
+ try {
+ Page page = getStartPage(aStartUrl, crawler, aReport);
+ TVGuide guide = createGuide(page, aReport);
+ PrintVisitor printer = new PrintVisitor(System.out);
+ guide.accept(printer);
+ processResults(programFilters, guide, aNotifier, aReport);
+ } catch (PageException e) {
+ aReport.addMessage("Problem getting TV guide", e);
+ LOG.info("Problem getting TV guide", e);
+ throw e;
+ }
+ aNotifier.send(aReport.asXml());
+ } finally {
+ System.out.println("Crawler finished");
+ }
+ }
+
+ /**
+ * Records interesting shows.
+ *
+ * @param aProgramCondition
+ * Condition determining which shows are interesting.
+ * @param aGuide
+ * Television guide.
+ * @throws MessagingException
+ * In case of problems sending a summary mail.
+ */
+ private void processResults(List<ProgramFilter> aProgramCondition,
+ TVGuide aGuide, Notifier aNotifier, Report aReport) {
+ ProgramActionExecutor executor = new ProgramActionExecutor(aReport);
+ for (ProgramFilter filter : aProgramCondition) {
+ List<Program> programs = filter.apply(aGuide);
+ ProgramAction action = filter.getAction();
+ for (Program program : programs) {
+ action.execute(program, executor);
+ }
+ }
+ executor.commit();
+
+ }
+
+ /**
+ * Creates the crawler.
+ *
+ * @param aCrawlerConfig
+ * Crawler configuration file.
+ * @param aOs
+ * Logging output stream for the crawler.
+ * @param aClient
+ * HTTP Client to use.
+ * @return Crawler.
+ * @throws FileNotFoundException
+ * In case configuration files cannot be found.
+ */
+ private Crawler createCrawler(String aCrawlerConfig, HttpClient aClient,
+ XslTransformer aTransformer) throws FileNotFoundException {
+ ConfigurationParser parser = new ConfigurationParser(aTransformer);
+ InputStream crawlerConfigFile = new FileInputStream(new File(
+ aCrawlerConfig));
+ Configuration config = parser.parse(crawlerConfigFile);
+ Crawler crawler = new CrawlerImpl(aClient, config);
+ return crawler;
+ }
+
+ /**
+ * Gets the start page of the electronic programme guide. This involves
+ * login and navigation to a suitable start page after logging in.
+ *
+ * @param aStartUrl
+ * URL of the electronic programme guide.
+ * @param aCrawler
+ * Crawler to use.
+ * @param aReport
+ * Report to use.
+ * @return Starting page.
+ */
+ private Page getStartPage(String aStartUrl, Crawler aCrawler, Report aReport)
+ throws PageException {
+ try {
+ Page page = aCrawler.getPage(aStartUrl, new NameValuePair[0]);
+ page = page.getAction("login").execute();
+ Action favorites = page.getAction("channels-favorites");
+ if (favorites == null) {
+ String msg = "Channels favorites action not found on start page";
+ throw new PageException(msg);
+ }
+ return favorites.execute();
+ } catch (PageException e) {
+ String msg = "Could not complete login to electronic programme guide.";
+ throw new PageException(msg, e);
+ }
+ }
+
+ /**
+ * Creates the TV guide by web crawling.
+ *
+ * @param aPage
+ * Starting page.
+ * @param aReport
+ * Report to use.
+ * @return TV guide.
+ * @throws PageException
+ * In case of problem getting the tv guide.
+ */
+ private TVGuide createGuide(Page aPage, Report aReport)
+ throws PageException {
+ LOG.info("Obtaining full TV guide");
+ Action[] actions = aPage.getActions();
+ if (actions.length == 0) {
+ LOG.error("No channels found");
+ throw new PageException("No channels found");
+ }
+ List<Channel> channels = new ArrayList<Channel>();
+ for (Action action : actions) {
+ try {
+ LOG.info("Getting channel info for '" + action.getName() + "'");
+ Action tomorrow = action.execute().getAction("tomorrow");
+ if (tomorrow == null) {
+ throw new PageException("Channel summary page for '"
+ + action.getName()
+ + "' does not contain required information");
+ }
+ Channel channel = createChannel(action.getName(), tomorrow
+ .execute(), aReport);
+ channels.add(channel);
+ if (SystemProperties.isDebugMode()) {
+ break; // Only one channel is crawled.
+ }
+ } catch (PageException e) {
+ aReport.addMessage("Could not create channel information for '"
+ + action.getName() + "'");
+ LOG.error("Could not create channel information for '"
+ + action.getName() + "'", e);
+ }
+ }
+ return new TVGuide(channels);
+ }
+
+ /**
+ * Create channel information for a specific channel.
+ *
+ * @param aChannel
+ * Channel name.
+ * @param aPage
+ * Starting page for the channel.
+ * @return Channel.
+ */
+ private Channel createChannel(String aChannel, Page aPage, Report aReport) {
+ LOG.info("Obtaining program for " + aChannel);
+ Action[] programActions = aPage.getActions();
+ List<Program> programs = new ArrayList<Program>();
+ for (Action action : programActions) {
+ String time = action.getContent().element("time").getText().trim();
+ Matcher matcher = _pattern.matcher(time);
+ if (matcher.matches()) {
+ Time begin = new Time(Integer.parseInt(matcher.group(1)),
+ Integer.parseInt(matcher.group(2)));
+ Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
+ .parseInt(matcher.group(4)));
+ TimeInterval interval = new TimeInterval(begin, end);
+ String description = "";
+ String keywords = "";
+
+ if (!SystemProperties.isNoProgramDetailsRequired()) {
+ Element descriptionElem = action.getContent().element(
+ "description");
+ if (descriptionElem == null) {
+ try {
+ Page programInfo = action.execute();
+ description = programInfo.getContent().element(
+ "description").getText().trim();
+ keywords = programInfo.getContent().element(
+ "keywords").getText().trim();
+ } catch (PageException e) {
+ String msg = "Program details could not be determined for '"
+ + action.getName() + "'";
+ aReport.addMessage(msg, e);
+ LOG.warn(msg, e);
+ }
+ } else {
+ description = descriptionElem.getTextTrim();
+ }
+ }
+ Program program = new Program(aChannel, action.getName(),
+ description, keywords, interval, action);
+
+ LOG.info("Got program " + program);
+ programs.add(program);
+ }
+ }
+ return new Channel(aChannel, programs);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action configured for a program.
+ */
+public interface ProgramAction {
+
+ /**
+ * Executes the action.
+ *
+ * @param aProgram
+ * Program to execute the action for.
+ * @param aExecutor
+ * Executor to use.
+ */
+ void execute(Program aProgram, ProgramActionExecutor aExecutor);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TimeInterval;
+import org.wamblee.crawler.kiss.guide.Program.RecordingResult;
+
+/**
+ * Provides execution of actions for programs. Actions use this class to tell
+ * the executor what to do. The executor then decides on exactly what to do and
+ * in what order and makes decisions in case of conflicts.
+ */
+public class ProgramActionExecutor {
+
+ private static final Log LOG = LogFactory
+ .getLog(ProgramActionExecutor.class);
+
+ /**
+ * Map of priority to set of programs.
+ */
+ private Map<Integer, Set<Program>> _showsToRecord;
+
+ /**
+ * Report to use.
+ */
+ private Report _report;
+
+ /**
+ * Constructs the program action executor.
+ *
+ * @param aReport Report to use.
+ */
+ public ProgramActionExecutor(Report aReport) {
+ _showsToRecord = new TreeMap<Integer, Set<Program>>();
+ _report = aReport;
+ }
+
+ /**
+ * Called by an action to indicate the desire to record a program.
+ *
+ * @param aPriority
+ * Priority of the program. Used to resolve conflicts.
+ * @param aProgram
+ * Program to record.
+ */
+ public void recordProgram(int aPriority, Program aProgram) {
+ LOG.info("priority = " + aPriority + ", program: " + aProgram);
+ // Putting -priority into the set makes sure that iteration order
+ // over the priorities will go from higher priority to lower priority.
+ Set<Program> programs = _showsToRecord.get(-aPriority);
+ if (programs == null) {
+ programs = new TreeSet<Program>(new Program.TimeComparator());
+ _showsToRecord.put(-aPriority, programs);
+ }
+ programs.add(aProgram);
+ }
+
+ /**
+ * Called by an action to indicate that a program is interesting.
+ *
+ * @param aCategory
+ * Category of the program.
+ * @param aProgram
+ * Program.
+ */
+ public void interestingProgram(String aCategory, Program aProgram) {
+ LOG.info("category = '" + aCategory + "', program: " + aProgram);
+ _report.interestingProgram(aCategory, aProgram);
+ }
+
+ /**
+ * Makes sure that the actions are performed.
+ */
+ public void commit() {
+ Set<TimeInterval> previouslyRecorded = new HashSet<TimeInterval>();
+ for (Integer priority : _showsToRecord.keySet()) {
+ for (Program program : _showsToRecord.get(priority)) {
+ TimeInterval interval = program.getInterval();
+ if (recordingConflictExists(previouslyRecorded, interval)) {
+ _report.setRecordingResult(RecordingResult.CONFLICT, program);
+ } else {
+ RecordingResult result = program.record();
+ _report.setRecordingResult(result, program);
+ previouslyRecorded.add(interval);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks an interval for overlap with a previously recorded program.
+ *
+ * @param aPreviouslyRecorded
+ * Previously recorded programs.
+ * @param aInterval
+ * Interval.
+ * @return True iff there is a recording conflict.
+ */
+ private boolean recordingConflictExists(
+ Set<TimeInterval> aPreviouslyRecorded, TimeInterval aInterval) {
+ for (TimeInterval recordedInterval : aPreviouslyRecorded) {
+ if (aInterval.overlap(recordedInterval)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.wamblee.conditions.AndCondition;
+import org.wamblee.conditions.Condition;
+import org.wamblee.conditions.PropertyRegexCondition;
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Parse the configuration of desired programs.
+ */
+class ProgramConfigurationParser {
+ private static final int DEFAULT_PRIORITY = 1;
+
+ // Configuration of interesting programs.
+
+ private static final String ELEM_PROGRAM = "program";
+
+ private static final String ELEM_PRIORITY = "priority";
+
+ private static final String ELEM_PATTERN = "match";
+
+ private static final String ELEM_ACTION = "action";
+
+ private static final String ELEM_CATEGORY = "category";
+
+ private static final String ACTION_NOTIFY = "notify";
+
+ private List<ProgramFilter> _filters;
+
+ ProgramConfigurationParser() {
+ _filters = null;
+ }
+
+ /**
+ * Parses the condition used to match the desired programs.
+ *
+ * @param aStream
+ * Input stream to parse from.
+ * @return Condition.
+ */
+ void parse(InputStream aStream) {
+ List<ProgramFilter> filters = new ArrayList<ProgramFilter>();
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(aStream);
+
+ Element root = document.getRootElement();
+
+ for (Iterator i = root.elementIterator(ELEM_PROGRAM); i.hasNext();) {
+ Element program = (Element) i.next();
+
+ Element categoryElem = program.element(ELEM_CATEGORY);
+ String category = "";
+ if (categoryElem != null) {
+ category = categoryElem.getText().trim();
+ }
+
+ Element actionElem = program.element(ELEM_ACTION);
+ int priority = DEFAULT_PRIORITY;
+ String priorityString = program.elementTextTrim(ELEM_PRIORITY);
+ if ( priorityString != null ) {
+ priority = Integer.valueOf(priorityString);
+ }
+ ProgramAction action = new RecordProgramAction(priority);
+ if (actionElem != null) {
+ if (actionElem.getText().equals(ACTION_NOTIFY)) {
+ action = new InterestingProgramAction(category);
+ }
+ }
+
+ List<Condition<Program>> regexConditions = new ArrayList<Condition<Program>>();
+ for (Iterator j = program.elementIterator(ELEM_PATTERN); j
+ .hasNext();) {
+ Element patternElem = (Element) j.next();
+ String fieldName = "name";
+ Attribute fieldAttribute = patternElem.attribute("field");
+ if (fieldAttribute != null) {
+ fieldName = fieldAttribute.getText();
+ }
+ String pattern = ".*(" + patternElem.getText() + ").*";
+ regexConditions.add(new PropertyRegexCondition<Program>(
+ fieldName, pattern, true));
+ }
+ Condition<Program> condition = new AndCondition<Program>(
+ regexConditions);
+ filters.add(new ProgramFilter(condition, action));
+ }
+ _filters = filters;
+ } catch (DocumentException e) {
+ throw new RuntimeException("Error parsing program configuraiton", e);
+ }
+ }
+
+ /**
+ * Returns the list of program filters.
+ *
+ * @return Filter list.
+ */
+ public List<ProgramFilter> getFilters() {
+ return _filters;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+import org.wamblee.crawler.kiss.guide.MatchVisitor;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TVGuide;
+
+/**
+ * Obtains a list of interesting programs from a TV guide and decides what to do
+ * with them.
+ */
+public class ProgramFilter {
+
+ private Condition<Program> _condition;
+
+ private ProgramAction _action;
+
+ /**
+ * Constructs the program filter.
+ * @param aCondition Condition used to find interesting programs.
+ * @param aAction Corresponding action to execute for matching programs.
+ */
+ public ProgramFilter(Condition<Program> aCondition, ProgramAction aAction) {
+ _condition = aCondition;
+ _action = aAction;
+ }
+
+ /**
+ * Gets the action.
+ * @return Action.
+ */
+ public ProgramAction getAction() {
+ return _action;
+ }
+
+ /**
+ * Applies the filter to a TV guide.
+ * @param aGuide TV guide.
+ * @return List of matching programs.
+ */
+ public List<Program> apply(TVGuide aGuide) {
+ MatchVisitor matcher = new MatchVisitor(_condition);
+ aGuide.accept(matcher);
+ return matcher.getMatches();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action to record a program.
+ */
+public class RecordProgramAction implements ProgramAction {
+
+ private int _priority;
+
+ /**
+ * Constructs the action.
+ * @param aPriority Priority of the recording action. Higher values have higher
+ * priority.
+ */
+ public RecordProgramAction(int aPriority) {
+ _priority = aPriority;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramAction#execute(org.wamblee.crawler.kiss.Program,
+ * org.wamblee.crawler.kiss.Report)
+ */
+ public void execute(Program aProgram, ProgramActionExecutor aReport) {
+ aReport.recordProgram(_priority, aProgram);
+ }
+
+}
--- /dev/null
+/*
+ *
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.Program.RecordingResult;
+
+/**
+ * Represents a report on the actions of the crawler.
+ */
+public class Report {
+
+ private static final Log LOG = LogFactory
+ .getLog(Report.class);
+
+ /**
+ * A map of category name to a set of program. Useful for displaying the
+ * output of possibly interesting programs on a per category basis.
+ */
+ private Map<String, Set<Program>> _interestingShows;
+
+ /**
+ * Map or recording result to a set of programs.
+ */
+ private EnumMap<RecordingResult, Set<Program>> _recordings;
+
+ /**
+ * Messages generated while doing all the work.
+ */
+ private List<String> _messages;
+
+ /**
+ * Constructs the report.
+ *
+ */
+ public Report() {
+ _interestingShows = new TreeMap<String, Set<Program>>();
+ _recordings = new EnumMap<RecordingResult, Set<Program>>(
+ RecordingResult.class);
+ for (RecordingResult result : RecordingResult.values()) {
+ _recordings.put(result, new TreeSet<Program>(
+ new Program.TimeComparator()));
+ }
+ _messages = new ArrayList<String>();
+ }
+
+ /**
+ * Adds a message.
+ *
+ * @param aMessage
+ * Message to add.
+ */
+ public void addMessage(String aMessage) {
+ _messages.add(aMessage);
+ }
+
+ /**
+ * Adds a message.
+ *
+ * @param aMessage
+ * Message to add.
+ * @param aException Exception that caused the problem.
+ */
+ public void addMessage(String aMessage, Exception aException) {
+ String msg = aMessage;
+ for (Throwable e = aException; e != null; e = e.getCause()) {
+ msg += ": " + e.getMessage();
+ }
+ addMessage(msg);
+ }
+
+ /**
+ * Called to indicate that a program is interesting.
+ *
+ * @param aCategory
+ * Category of the program.
+ * @param aProgram
+ * Program.
+ */
+ public void interestingProgram(String aCategory, Program aProgram) {
+ LOG.info("category = '" + aCategory + "', program: " + aProgram);
+ Set<Program> programs = _interestingShows.get(aCategory);
+ if (programs == null) {
+ programs = new TreeSet<Program>(new Program.TimeComparator());
+ _interestingShows.put(aCategory, programs);
+ }
+ programs.add(aProgram);
+ }
+
+ /**
+ * Called to specify the result of recording a program.
+ * @param aResult Result.
+ * @param aProgram Program.
+ */
+ public void setRecordingResult(RecordingResult aResult, Program aProgram) {
+ _recordings.get(aResult).add(aProgram);
+ }
+
+
+ /**
+ * Get report as XML.
+ *
+ * @return XML report
+ */
+ public Element asXml() {
+ DocumentFactory factory = DocumentFactory.getInstance();
+ Element report = factory.createElement("report");
+
+ if (_messages.size() > 0) {
+ Element messages = report.addElement("messages");
+ for (String message : _messages) {
+ messages.addElement("message").setText(message);
+ }
+ }
+
+ Set<Program> reportedPrograms = new HashSet<Program>();
+
+ for (RecordingResult result : RecordingResult.values()) {
+ if (_recordings.get(result).size() > 0) {
+ Element recordingResult = report.addElement("recorded")
+ .addAttribute("result", result.toString());
+
+ for (Program program : _recordings.get(result)) {
+ recordingResult.add(program.asXml());
+ reportedPrograms.add(program);
+ }
+ }
+ }
+
+ if (_interestingShows.size() > 0) {
+ Element interesting = report.addElement("interesting");
+ for (String category : _interestingShows.keySet()) {
+ Element categoryElem = interesting;
+ if (category.length() > 0) {
+ categoryElem = interesting.addElement("category");
+ categoryElem.addAttribute("name", category);
+ }
+ for (Program program : _interestingShows.get(category)) {
+ if (!reportedPrograms.contains(program)) {
+ categoryElem.add(program.asXml());
+ } else {
+ LOG.info("Category '" + category + "', program "
+ + program + " already reported");
+ }
+ }
+ if (categoryElem.elements().size() == 0) {
+ // Remove empty category element.
+ LOG
+ .info("Removing element for category '" + category
+ + "'");
+ interesting.remove(categoryElem);
+ }
+ }
+
+ }
+
+ return report;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.general.SpringBeanFactory;
+
+/**
+ * Bean factory used for the standalone crawler application.
+ */
+public class StandaloneCrawlerBeanFactory extends SpringBeanFactory {
+
+ private static final String LOCATOR = "crawler-standalone.xml";
+ private static final String FACTORY_NAME = "crawlerStandalone";
+
+ /**
+ * Constructs the factory.
+ *
+ */
+ public StandaloneCrawlerBeanFactory() {
+ super(LOCATOR, FACTORY_NAME);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+/**
+ * Access to system properties for the crawler.
+ */
+public final class SystemProperties {
+
+ private static final String DEBUG_PROPERTY = "kiss.debug";
+
+ private static final String NO_PROGRAM_DETAILS = "kiss.nodetails";
+
+ private static final String DISABLE_RECORD = "kiss.norecord";
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private SystemProperties() {
+ // Empty.
+ }
+
+ /**
+ * Determines if the system is run in debug mode. When in debug mode, less
+ * extensive crawling is done.
+ *
+ * @return True iff we are running in debug mode.
+ */
+ public static boolean isDebugMode() {
+ return System.getProperties().getProperty(DEBUG_PROPERTY) != null;
+ }
+
+ /**
+ * Determines if no program details are required.
+ *
+ * @return True iff no program details are required.
+ */
+ public static boolean isNoProgramDetailsRequired() {
+ return System.getProperties().getProperty(NO_PROGRAM_DETAILS) != null;
+ }
+
+ /**
+ * Determines if recording is disabled.
+ *
+ * @return True iff no recording should be done.
+ */
+ public static boolean isRecordDisabled() {
+ return System.getProperties().getProperty(DISABLE_RECORD) != null;
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package contains the crawling logic of the KiSS EPG site as well
+as the configuration classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.dom4j.Element;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * A notifier that uses SMTP to notify users by mail.
+ *
+ */
+public class MailNotifier implements Notifier {
+
+ private String _from;
+
+ private String _to;
+
+ private String _subject;
+
+ private String _htmlXslt;
+
+ private String _textXslt;
+
+ private MailServer _server;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the notifier.
+ *
+ * @param aFrom
+ * Sender mail address to use.
+ * @param aTo
+ * Recipient mail address to use.
+ * @param aSubject
+ * Subject to use in the email.
+ * @param aHtmlXslt
+ * XSLT file to transform the report into HTML.
+ * @param aTextXslt
+ * XSLT file to transform the report into text.
+ * @param aServer
+ * Mail server to use.
+ * @param aTransformer Transformer to use.
+ */
+ public MailNotifier(String aFrom, String aTo, String aSubject,
+ String aHtmlXslt, String aTextXslt, MailServer aServer, XslTransformer aTransformer) {
+ _from = aFrom;
+ _to = aTo;
+ _subject = aSubject;
+ _htmlXslt = aHtmlXslt;
+ _textXslt = aTextXslt;
+ _server = aServer;
+ _transformer = aTransformer;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.Notifier#send(org.dom4j.Element)
+ */
+ public void send(Element aReport) throws NotificationException {
+ HtmlEmail mail = new HtmlEmail();
+ try {
+ mail.setFrom(_from);
+ mail
+ .setTo(Arrays
+ .asList(new InternetAddress[] { new InternetAddress(
+ _to) }));
+ mail.setSentDate(new Date());
+ mail.setSubject(_subject);
+
+ String htmlText = transformReport(aReport, _htmlXslt);
+ String plainText = transformReport(aReport, _textXslt);
+
+ mail.setHtmlMsg(htmlText);
+ mail.setTextMsg(plainText);
+
+ _server.send(mail);
+ } catch (EmailException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (TransformerException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (AddressException e) {
+ throw new NotificationException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Transforms a report into a destination format.
+ *
+ * @param aReport
+ * Report to transform
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed result.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case of problems transforming.
+ */
+ private String transformReport(Element aReport, String aXslt)
+ throws IOException, TransformerException {
+ String reportXmlText = aReport.asXML();
+ return _transformer.textTransform(reportXmlText.getBytes(), _transformer.resolve(aXslt));
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.notification.Notifier#asHtml(org.dom4j.Element)
+ */
+ public String asHtml(Element aReport) throws IOException, TransformerException {
+ return transformReport(aReport, _htmlXslt);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.notification.Notifier#asText(org.dom4j.Element)
+ */
+ public String asText(Element aReport) throws IOException, TransformerException {
+ return transformReport(aReport, _textXslt);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+
+/**
+ * Mail server.
+ */
+public class MailServer {
+
+ private String _host;
+
+ private int _port;
+
+ private String _username;
+
+ private String _password;
+
+ /**
+ * Constructs the mail server.
+ *
+ * @param aHost
+ * Host name of the SMTP server.
+ * @param aPort
+ * Port name of the SMTP server.
+ * @param aUsername
+ * Username to use for authentication or null if no
+ * authentication is required.
+ * @param aPassword
+ * Password to use for authentication or null if no authenticatio
+ * is required.
+ */
+ public MailServer(String aHost, int aPort, String aUsername,
+ String aPassword) {
+ _host = aHost;
+ _port = aPort;
+ _username = aUsername;
+ _password = aPassword;
+ }
+
+ /**
+ * Sends an e-mail.
+ *
+ * @param aMail
+ * Mail to send.
+ * @throws EmailException
+ * In case of problems sending the mail.
+ */
+ public void send(Email aMail) throws EmailException {
+ Properties props = new Properties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.host", _host);
+ props.put("mail.smtp.port", "" + _port);
+
+ Session mailSession = Session.getInstance(props,
+ new UsernamePasswordAuthenticator(_username, _password));
+ aMail.setMailSession(mailSession);
+ aMail.send();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+/**
+ * Notification exception thrown in case of problems sending a notification to a
+ * user.
+ *
+ */
+public class NotificationException extends Exception {
+
+ /**
+ * Constructs the notification.
+ *
+ * @param aMsg
+ * Message.
+ */
+ public NotificationException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the notification.
+ *
+ * @param aMsg
+ * Message.
+ * @param aCause
+ * Cause.
+ */
+ public NotificationException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.dom4j.Element;
+
+/**
+ * Object used to send notifications about the actions of the crawler.
+ *
+ */
+public interface Notifier {
+
+ /**
+ * Sends a notification.
+ *
+ * @param aReport
+ * Report to send.
+ */
+ void send(Element aReport) throws NotificationException;
+
+ /**
+ * Converts the report to html.
+ * @param aReport Report to convert.
+ * @return
+ */
+ String asHtml(Element aReport) throws IOException, TransformerException;
+
+ /**
+ * Converts the report to text.
+ * @param aReport Report to convert.
+ * @return
+ */
+ String asText(Element aReport) throws IOException, TransformerException;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+
+/**
+ * Authenticator to supply username and password to the mail server (if needed).
+ *
+ */
+public class UsernamePasswordAuthenticator extends Authenticator {
+
+ private String _username;
+
+ private String _password;
+
+ /**
+ * Constructs the authenticator.
+ *
+ * @param aUsername
+ * User name.
+ * @param aPassword
+ * Password.
+ */
+ public UsernamePasswordAuthenticator(String aUsername, String aPassword) {
+ _username = aUsername;
+ _password = aPassword;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.mail.Authenticator#getPasswordAuthentication()
+ */
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ if (_username == null) {
+ return null;
+ }
+ return new PasswordAuthentication(_username, _password);
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+Contains the classes for notifying users of the results of crawling.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides a crawler for the KiSS electronic program guide.
+It provides automatic recording of programs that satisfy criteria specified
+by the user.
+
+The following packages are defined:
+<ul>
+ <li> {@link org.wamblee.crawler.kiss.main}: Contains the crawling functionality and
+ configuration classes.
+ </li>
+ <li> {@link org.wamblee.crawler.kiss.guide}: Contains the TV guide object model and the
+ classes for searching relevant programs in the guide.
+ </li>
+ <li> {@link org.wamblee.crawler.kiss.notification}: Contains the classes for
+ notification.
+ </li>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="program-info">
+ <xsl:apply-templates select="//xhtml:a"/>
+ <xsl:element name="title">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="//xhtml:tr[1]/xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:element name="keywords">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="//xhtml:tr[3]/xhtml:td[3]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:element name="description">
+ <xsl:apply-templates select="//xhtml:tr[7]/xhtml:td[1]"/>
+ </xsl:element>
+ </xsl:element>
+ </xsl:template>
+
+
+
+ <xsl:template match="xhtml:a[ text() = 'Record' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:text>record</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>recorded</xsl:text>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="program-info">
+ <xsl:apply-templates select="//xhtml:a"/>
+ <xsl:element name="title">
+ <xsl:value-of select="//xhtml:h2[1]"/>
+ </xsl:element>
+ <xsl:element name="keywords">
+ <xsl:apply-templates select="//text()[count(preceding::xhtml:br)= 1]"/>
+ </xsl:element>
+ <xsl:element name="description">
+ <xsl:apply-templates select="//text()[count(preceding::xhtml:h2) = 2 and
+ count(following::xhtml:br) >= 4 and count(preceding::xhtml:br) >= 3]"/>
+ </xsl:element>
+ </xsl:element>
+ </xsl:template>
+
+
+
+ <xsl:template match="xhtml:a[ text() = 'Record' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:text>record</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>recorded</xsl:text>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:template match="/">
+ <xsl:element name="result">
+ <xsl:choose>
+ <xsl:when test="count(//xhtml:h1[contains(text(), 'Error')]) = 1">
+ <xsl:choose>
+ <xsl:when test="count(//xhtml:body/text()[contains(., 'already in the')]) =
+ 1">
+ <xsl:text>DUPLICATE</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(//xhtml:body/text()[contains(., 'conflicts with a
+ recording')]) =
+ 1">
+ <xsl:text>CONFLICT</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ERROR</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>OK</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:body">
+ <xsl:value-of select="text()"/>
+ </xsl:template>
+
+ <xsl:template match="text()">
+ TEXT NODE
+ <xsl:value-of select="."/>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="messages"/>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+
+ <xsl:template match="messages">
+ <h2>Messages</h2>
+ <ul>
+ <xsl:for-each select="message">
+ <li><font size="-1">
+ <xsl:value-of select="."/>
+ </font>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:include href="utilities.xsl"/>
+ <xsl:template match="report">
+ <xsl:text>KiSS crawler report</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <xsl:text>Possibly interesting programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="messages"/>
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <xsl:apply-templates select="program"/>
+ </xsl:template>
+ <xsl:template match="recorded">
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for technical reasons.</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <xsl:text>*</xsl:text>
+ <xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="name"/>
+ <xsl:text>(</xsl:text>
+ <xsl:value-of select="channel"/>
+ <xsl:text>/</xsl:text><xsl:value-of select="keywords"/>
+ <xsl:text>)</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:variable name="indent">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+ <xsl:call-template name="indent">
+ <xsl:with-param name="src">
+ <xsl:call-template name="word-wrap">
+ <xsl:with-param name="src"><xsl:value-of select="description"/></xsl:with-param>
+ <xsl:with-param name="width"><xsl:value-of select="72"/></xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="indentString">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+
+ </xsl:call-template>
+
+ <!--
+ <xsl:value-of select="$indent"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src"><xsl:value-of select="description"/></xsl:with-param>
+ <xsl:with-param name="from"><xsl:value-of select="$newline"/></xsl:with-param>
+ <xsl:with-param name="to"><xsl:value-of select="$newline"/><xsl:value-of select="$indent"/></xsl:with-param>
+ </xsl:call-template>
+ -->
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <xsl:text>Category: </xsl:text><xsl:value-of select="@name"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+
+ <xsl:template match="messages">
+ <xsl:text>Messages</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:for-each select="message">
+ <xsl:text>* </xsl:text>
+ <xsl:value-of select="."/>
+ <xsl:value-of select="$newline"/>
+ </xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+Manifest-Version: 1.0\r
+Class-Path: \r
+\r
--- /dev/null
+
+<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>KiSS Crawler overview page</title>
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="expires" content="0">
+ <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
+ <meta http-equiv="description" content="This is my page">
+
+ <!--
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ -->
+ </head>
+
+ <body>
+ <h1>KiSS Crawler Overview</h1>
+
+ <TABLE border="1">
+ <tr>
+ <td>
+ Currently running:
+ </td>
+ <td>
+ <c:out value="${running}"/>
+ </td>
+ </tr>
+ <c:if test="${lastReport != null}">
+ <tr>
+ <td>
+ Last executed at:
+ </td>
+ <td>
+ <c:out value="${lastExecuted}"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last result:
+ </td>
+ <td>
+ <c:out value="${lastResult}"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last message:
+ </td>
+ <td>
+ <c:out value="${lastMessage}" escapeXml="false"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last report:
+ </td>
+ <td>
+ <a href="?details=1">details</a>
+ </td>
+ </tr>
+ </c:if>
+ </TABLE>
+ <c:if test="${!running}">
+ <FORM action="runnow">
+ <INPUT type="submit" name="runnow" value="Run Crawler as soon as possible">
+ </FORM>
+ </c:if>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.4"
+ xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+ http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+ <listener>
+ <listener-class>org.wamblee.crawler.kiss.servlet.Application</listener-class>
+ </listener>
+
+ <servlet>
+ <servlet-name>CrawlerServlet</servlet-name>
+ <servlet-class>org.wamblee.crawler.kiss.servlet.CrawlerServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>CrawlerServlet</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+</web-app>
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../../build/trailer.xml">
+ <!ENTITY crawlerdeps SYSTEM "file:../basic/deps.xml">
+ <!ENTITY kisscrawlerdeps SYSTEM "file:../kiss/deps.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value="../.."/>
+ <property name="module.name" value="wamblee-crawler-kissweb" />
+ <property name="webroot.dir" value="WebRoot"/>
+
+ &header;
+ &crawlerdeps;
+ &kisscrawlerdeps;
+
+ <target name="module.build.deps"
+ depends="kisscrawler.src.d,servletapi.d,wamblee.kisscrawler.d,quartz.d,spring.d,jstl.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="kisscrawler.test.d,wamblee.kisscrawler.test.d">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="crawler"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.crawler.properties.xml</value>
+ <value>org.wamblee.crawler.notification.xml</value>
+ <value>org.wamblee.crawler.kiss.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+##############################################################################
+# Class name of the beanfactory used by the crawler application
+##############################################################################
+
+org.wamblee.beanfactory.class=org.wamblee.crawler.kiss.spring.CrawlerBeanFactory
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <!-- The object that tells quartz how to schedule the crawler -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerScheduler"
+ class="org.wamblee.crawler.kiss.scheduling.quartz.QuartzCrawlerScheduler">
+ <constructor-arg><value type="int">3600</value></constructor-arg>
+ </bean>
+
+ <!-- The object which executes the crawler -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerExecutor"
+ class="org.wamblee.crawler.kiss.scheduling.CrawlerExecutorImpl">
+ <constructor-arg><value>${org.wamblee.crawler.config.epg}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.config.programs}</value></constructor-arg>
+ <constructor-arg><ref bean="org.wamblee.crawler.kiss.notification.Notifier"/></constructor-arg>
+ </bean>
+
+ <!-- The object that determines whether to execute the crawler when it is signalled by
+ the scheduler. -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerStatus"
+ class="org.wamblee.crawler.kiss.scheduling.CrawlerStatus">
+ <constructor-arg><ref local="org.wamblee.crawler.kiss.scheduling.CrawlerExecutor"/></constructor-arg>
+ <!-- The interval of the day in hours [hourmin, hourmax] over which crawling will be done and
+ retried if necessary -->
+ <constructor-arg><value type="int">19</value></constructor-arg>
+ <constructor-arg><value type="int">24</value></constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+
+
+############################################################################
+# Mail server configuration
+############################################################################
+org.wamblee.crawler.smtp.host=shikra
+org.wamblee.crawler.smtp.port=25
+org.wamblee.crawler.smtp.username=
+org.wamblee.crawler.smtp.password=
+
+############################################################################
+# Mail notification configuration
+############################################################################
+org.wamblee.crawler.notification.from=kiss@wamblee.org
+org.wamblee.crawler.notification.to=erik@brakkee.org
+org.wamblee.crawler.notification.subject=Recording summary for tomorrow
+
+############################################################################
+# Configuration of the crawler
+############################################################################
+org.wamblee.crawler.config.epg=/home/erik/crawler/config.xml
+org.wamblee.crawler.config.programs=/home/erik/crawler/programs.xml
+
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.util.Date;
+
+import org.wamblee.crawler.kiss.main.Report;
+
+/**
+ * Encapsulates the actual execution of the crawler.
+ * This interface makes it possible to test the scheduling logic
+ * in isolation.
+ *
+ */
+public interface CrawlerExecutor {
+
+ /**
+ * Executes the crawler.
+ * @param aDate Date the crawler is being triggered.
+ * @param The report from the crawler.
+ * @throws Exception
+ */
+ void execute(Date aDate, Report aReport) throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.util.Date;
+
+import org.wamblee.crawler.kiss.main.KissCrawler;
+import org.wamblee.crawler.kiss.main.Report;
+import org.wamblee.crawler.kiss.notification.Notifier;
+
+/**
+ * Implementation which executes the KiSS crawler for retrieving web content.
+ */
+public class CrawlerExecutorImpl implements CrawlerExecutor {
+
+ private String _crawlerConfig;
+ private String _programConfig;
+ private Notifier _notifier;
+
+ /**
+ * Constructs the crawler executor.
+ * @param aCrawlerConfig Crawler configuration file.
+ * @param aProgramConfig Program configuration file.
+ * @param aNotifier Object used to send notifications.
+ */
+ public CrawlerExecutorImpl(String aCrawlerConfig, String aProgramConfig, Notifier aNotifier) {
+ _crawlerConfig = aCrawlerConfig;
+ _programConfig = aProgramConfig;
+ _notifier = aNotifier;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler.CrawlerExecutor#execute(java.util.Date)
+ */
+ public void execute(Date aDate, Report aReport) throws Exception {
+ new KissCrawler(_crawlerConfig, _programConfig, _notifier, aReport);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+/**
+ * Interface to the scheduler specific for working with the crawler.
+ */
+public interface CrawlerScheduler {
+
+ /**
+ * Initializes the scheduler.
+ * @throws Exception In case of problems.
+ */
+ void initialize() throws Exception;
+
+ /**
+ * Checks if the crawler is running.
+ * @return True iff the crawler is running.
+ * @throws Exception In case of problems.
+ */
+ boolean isCrawlerRunning() throws Exception;
+
+ /**
+ * Schedules the crawler for immediate execution.
+ * @throws Exception In case of problems.
+ */
+ void scheduleNow() throws Exception;
+
+ /**
+ * Shuts down the scheduler.
+ * @throws Exception In case of problems.
+ */
+ void shutdown() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.crawler.kiss.main.Report;
+
+/**
+ * This class encapsulates the logic for deciding whether to
+ * run the crawler. This provides the mechanism to keep the
+ * scheduler simple (e.g. scheduling every hour) and providing
+ * more complex logic for determining whether to run the
+ * crawler.
+ */
+public class CrawlerStatus implements Serializable {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerStatus.class);
+
+ private CrawlerExecutor _crawler;
+ private Date _lastExecuted;
+ private boolean _lastResult;
+ private Exception _lastException;
+ private Report _lastReport;
+ private int _hourMin;
+ private int _hourMax;
+ private boolean _mustExecute;
+
+ /**
+ * Constructs the scheduler.
+ * The crawler will run if it is triggered in the range between the minimum (included)
+ * and maximum (included) hour of the day if either
+ * <ul>
+ * <li>it is triggered for the first time on the current day.</li>
+ * <li>an earlier crawling attempt on the same day failed. </li>
+ * </ul>
+ * @param aCrawler The interface through which the crawler is executed.
+ * @param aHourMin The crawler may only run if hour >= <code>aHourMin</code>
+ * @param aHourMax The crawler may only run if hour <= <code>aHourMax</code>
+ */
+ public CrawlerStatus(CrawlerExecutor aCrawler, int aHourMin, int aHourMax) {
+ _crawler = aCrawler;
+ _lastExecuted = new Date();
+ _lastResult = true; // the crawler will automatically run the next day.
+ _lastException = null;
+ _lastReport = null;
+ _hourMin = aHourMin;
+ _hourMax = aHourMax;
+ _mustExecute = false;
+ }
+
+ /**
+ * Determines whether or not the crawler must be run the next time it is triggered.
+ * @param aMustExecute If true then the crawler will run the next time it is triggered
+ * by the scheduler.
+ */
+ public void setMustExecute(boolean aMustExecute) {
+ _mustExecute = aMustExecute;
+ }
+
+ /**
+ * Called by a scheduled job. This determines whether the crawler must be run or
+ * not. This encapsulates the rukes for retrying and scheduling the crawler.
+ * @param aDate Time at which we are executing now.
+ */
+ public void execute(Date aDate) {
+
+ if (mustExecute(aDate)) {
+ LOG.info("Executing crawler at " + aDate);
+ Report report = new Report();
+ try {
+ _crawler.execute(aDate, report);
+ _lastResult = true;
+ _lastException = null;
+ } catch (Exception e) {
+ _lastResult = false;
+ _lastException = e;
+ } finally {
+ _lastExecuted = aDate;
+ _lastReport = report;
+ }
+ }
+ }
+
+ /**
+ * Gets the time the crawler was last executed.
+ * @return Time of last execution.
+ */
+ public Date getLastExecuted() {
+ return _lastExecuted;
+ }
+
+ /**
+ * Gets the result of the last execution.
+ * @return True iff last execution was a success.
+ */
+ public boolean getLastResult() {
+ return _lastResult;
+ }
+
+ /**
+ * Gets the exception thrown by the last execution.
+ * @return null if the last execution was successful or an exception
+ * otherwise.
+ */
+ public Exception getLastException() {
+ return _lastException;
+ }
+
+ /**
+ * Gets the last report from the scheduler.
+ * @return Report.
+ */
+ public Report getLastReport() {
+ return _lastReport;
+ }
+
+ /**
+ * Determines whether or not the crawler must be run.
+ * @param aDate Current time.
+ * @return True iff the crawler must be run.
+ */
+ private boolean mustExecute(Date aDate) {
+ if (_mustExecute) {
+ _mustExecute = false;
+ return true;
+ }
+ if ( _lastExecuted == null ) {
+ return false; // crawler must be started manually at least once after deployment.
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(aDate);
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ if ( hour < _hourMin ) {
+ return false;
+ }
+ if (hour > _hourMax ) {
+ return false;
+ }
+
+ if ( !lastExecutionWasOnSameDay(aDate)) {
+ return true; // First execution of today.
+ }
+ // last execution was on the same day.
+ if ( !_lastResult ) {
+ return true; // last execution of today was unsuccessful, retry.
+ }
+ return false; // already run successfully today.
+ }
+
+ /**
+ * Determines if the last execution was on the same day.
+ * @param aDate Current time.
+ * @return True iff last execution was on the same day.
+ */
+ private boolean lastExecutionWasOnSameDay(Date aDate) {
+ if ( _lastExecuted == null ) {
+ return false;
+ }
+ int curDay = getDayOfYear(aDate);
+ int lastDay = getDayOfYear(_lastExecuted);
+ return curDay == lastDay; // check can be invalid only if scheduling interval is one year,
+ // which is ridiculous.
+ }
+
+ /**
+ * Gets the day of the year
+ * @param aDate Date to compute day for.
+ */
+ private int getDayOfYear(Date aDate) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(aDate);
+ return calendar.get(Calendar.DAY_OF_YEAR);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling.quartz;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.StatefulJob;
+import org.wamblee.crawler.kiss.scheduling.CrawlerStatus;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * Quartz job to execute the crawler.
+ */
+public class CrawlerJob implements StatefulJob {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerJob.class);
+
+ /**
+ * Constructs the job.
+ *
+ */
+ public CrawlerJob() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
+ */
+ public void execute(JobExecutionContext aContext)
+ throws JobExecutionException {
+ LOG.info("Job triggered");
+ try {
+ CrawlerStatus schedule = BeanKernel.getBeanFactory().find(
+ CrawlerStatus.class);
+ schedule.execute(aContext.getFireTime());
+ } catch (Exception e) {
+ throw new JobExecutionException("Error executing crawler", e, false);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling.quartz;
+
+import java.util.Date;
+import java.util.List;
+
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerFactory;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
+import org.quartz.TriggerUtils;
+import org.quartz.impl.StdSchedulerFactory;
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+
+/**
+ * Interface to the Quartz scheduler.
+ */
+public class QuartzCrawlerScheduler implements CrawlerScheduler {
+
+ /**
+ *
+ */
+ private static final String TRIGGER_NAME = "interval";
+
+ /**
+ *
+ */
+ private static final String JOB_NAME = "kisscrawler";
+
+ private Scheduler _scheduler;
+
+ private int _intervalInSeconds;
+
+ /**
+ * Constructs the quartz interface.
+ * @param aIntervalInSeconds Scheduling interval in seconds.
+ * @throws SchedulerException
+ */
+ public QuartzCrawlerScheduler(int aIntervalInSeconds) throws SchedulerException {
+ SchedulerFactory schedulerFactory = new StdSchedulerFactory();
+ _scheduler = schedulerFactory.getScheduler();
+ _intervalInSeconds = aIntervalInSeconds;
+ }
+
+ /**
+ * Initializes the scheduler.
+ * @throws SchedulerException
+ */
+ public void initialize() throws SchedulerException {
+ _scheduler.start();
+
+ JobDetail jobDetail = new JobDetail(JOB_NAME, null, CrawlerJob.class);
+ Trigger trigger = TriggerUtils.makeSecondlyTrigger(_intervalInSeconds);
+ //trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));
+ trigger.setStartTime(new Date());
+ trigger.setName(TRIGGER_NAME);
+
+ _scheduler.scheduleJob(jobDetail, trigger);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler#isCrawlerRunning()
+ */
+ public boolean isCrawlerRunning() throws Exception {
+ List jobs = _scheduler.getCurrentlyExecutingJobs();
+ return jobs.size() > 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler#scheduleNow()
+ */
+ public void scheduleNow() throws Exception {
+ Trigger trigger = new SimpleTrigger("immediate", null);
+ trigger.setJobName(JOB_NAME);
+ _scheduler.scheduleJob(trigger);
+ }
+
+ /**
+ * Shuts down the scheduler.
+ * @throws SchedulerException
+ */
+ public void shutdown() throws SchedulerException {
+ _scheduler.shutdown();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.servlet;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * The mechanism for kick starting the scheduling of the KiSS crawler.
+ */
+public class Application implements ServletContextListener {
+
+ /**
+ * Constructs the listener.
+ *
+ */
+ public Application() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+ */
+ public void contextInitialized(ServletContextEvent aEvent) {
+ aEvent.getServletContext().log("KiSS Crawler initializing");
+ try {
+ getScheduler().initialize();
+ } catch (Exception e) {
+ aEvent.getServletContext().log("Error scheduling job", e);
+ return;
+ }
+ aEvent.getServletContext().log("KiSS Crawler initialized");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+ */
+ public void contextDestroyed(ServletContextEvent aEvent) {
+ aEvent.getServletContext().log("KiSS Crawler shutting down");
+ try {
+ getScheduler().shutdown();
+ } catch (Exception e) {
+ aEvent.getServletContext().log("Error scheduling job", e);
+ return;
+ }
+ aEvent.getServletContext().log("KiSS Crawler shut down complete");
+ }
+
+ /**
+ * Gets the scheduler from Spring.
+ * @return Scheduler.
+ */
+ private CrawlerScheduler getScheduler() {
+ return BeanKernel.getBeanFactory().find(CrawlerScheduler.class);
+ }
+
+ public static void main(String[] aArgs) throws Exception {
+ Application application = new Application();
+ application.getScheduler().initialize();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.servlet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.wamblee.crawler.kiss.main.Report;
+import org.wamblee.crawler.kiss.notification.Notifier;
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+import org.wamblee.crawler.kiss.scheduling.CrawlerStatus;
+import org.wamblee.general.BeanKernel;
+
+/**
+ *
+ */
+public class CrawlerServlet extends HttpServlet {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doPost(HttpServletRequest aRequest,
+ HttpServletResponse aResponse) throws ServletException, IOException {
+
+ CrawlerScheduler scheduler = BeanKernel.getBeanFactory().find(
+ CrawlerScheduler.class);
+ CrawlerStatus status = BeanKernel.getBeanFactory().find(
+ CrawlerStatus.class);
+
+ try {
+ if (aRequest.getParameter("details") != null) {
+ Report report = status.getLastReport();
+ if (report != null) {
+ Notifier notifier = BeanKernel.getBeanFactory().find(Notifier.class);
+ aResponse.setContentType("text/html");
+ OutputStream os = aResponse.getOutputStream();
+ os.write(notifier.asHtml(report.asXml()).getBytes());
+ return;
+ }
+ }
+ if (aRequest.getParameter("runnow") != null) {
+ status.setMustExecute(true);
+ scheduler.scheduleNow();
+ aResponse.sendRedirect("");
+ return;
+ }
+ aRequest.setAttribute("running", scheduler.isCrawlerRunning());
+ aRequest.setAttribute("lastExecuted", status.getLastExecuted());
+ aRequest.setAttribute("lastResult", status.getLastResult());
+ aRequest.setAttribute("lastException", status.getLastException());
+ aRequest.setAttribute("lastReport", status.getLastReport());
+ String msg = "";
+ Throwable e = status.getLastException();
+ while (e != null) {
+ msg = msg + e.getClass().getName() + ": " + e.getMessage()
+ + "<br/>";
+ e = e.getCause();
+ }
+ aRequest.setAttribute("lastMessage", msg);
+ } catch (Exception e) {
+ throw new ServletException("Error getting status", e);
+ }
+ aRequest.getRequestDispatcher("WEB-INF/overview.jsp").forward(aRequest,
+ aResponse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doGet(HttpServletRequest aRequest,
+ HttpServletResponse aResponse) throws ServletException, IOException {
+ doPost(aRequest, aResponse);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.spring;
+import org.wamblee.general.SpringBeanFactory;
+
+
+/**
+ * Bean factory for the crawler application.
+ */
+public class CrawlerBeanFactory extends SpringBeanFactory {
+ private static final String SELECTOR_NAME = "beanRefContext.xml";
+ private static final String FACTORY_NAME = "crawler";
+
+ /**
+ * Constructs the bean factory.
+ *
+ */
+ public CrawlerBeanFactory() {
+ super(SELECTOR_NAME, FACTORY_NAME);
+ }
+}
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+##############
+# Properties used by forrest.build.xml for building the website
+# These are the defaults, un-comment them only if you need to change them.
+##############
+
+# Prints out a summary of Forrest settings for this project
+#forrest.echo=true
+
+# Project name (used to name .war file)
+#project.name=my-project
+
+# Specifies name of Forrest skin to use
+# See list at http://forrest.apache.org/docs/skins.html
+project.skin=tigris
+
+# Descriptors for plugins and skins
+# comma separated list, file:// is supported
+#forrest.skins.descriptors=http://forrest.apache.org/skins/skins.xml,file:///c:/myskins/skins.xml
+#forrest.plugins.descriptors=http://forrest.apache.org/plugins/plugins.xml,http://forrest.apache.org/plugins/whiteboard-plugins.xml
+
+##############
+# behavioural properties
+#project.menu-scheme=tab_attributes
+#project.menu-scheme=directories
+
+##############
+# layout properties
+
+# Properties that can be set to override the default locations
+#
+# Parent properties must be set. This usually means uncommenting
+# project.content-dir if any other property using it is uncommented
+
+#project.status=status.xml
+project.content-dir=docs
+#project.raw-content-dir=${project.content-dir}/content
+#project.conf-dir=${project.content-dir}/conf
+#project.sitemap-dir=${project.content-dir}
+#project.xdocs-dir=${project.content-dir}/content/xdocs
+project.resources-dir=${project.content-dir}/resources
+#project.stylesheets-dir=${project.resources-dir}/stylesheets
+#project.images-dir=${project.resources-dir}/images
+#project.schema-dir=${project.resources-dir}/schema
+#project.skins-dir=${project.content-dir}/skins
+#project.skinconf=${project.content-dir}/skinconf.xml
+#project.lib-dir=${project.content-dir}/lib
+project.classes-dir=${project.resources-dir}/properties
+#project.translations-dir=${project.content-dir}/translations
+
+
+
+##############
+# validation properties
+
+# This set of properties determine if validation is performed
+# Values are inherited unless overridden.
+# e.g. if forrest.validate=false then all others are false unless set to true.
+#forrest.validate=true
+#forrest.validate.xdocs=${forrest.validate}
+#forrest.validate.skinconf=${forrest.validate}
+#forrest.validate.sitemap=${forrest.validate}
+#forrest.validate.stylesheets=${forrest.validate}
+#forrest.validate.skins=${forrest.validate}
+#forrest.validate.skins.stylesheets=${forrest.validate.skins}
+
+# *.failonerror=(true|false) - stop when an XML file is invalid
+#forrest.validate.failonerror=true
+
+# *.excludes=(pattern) - comma-separated list of path patterns to not validate
+# e.g.
+#forrest.validate.xdocs.excludes=samples/subdir/**, samples/faq.xml
+#forrest.validate.xdocs.excludes=
+
+
+##############
+# General Forrest properties
+
+# The URL to start crawling from
+#project.start-uri=linkmap.html
+
+# Set logging level for messages printed to the console
+# (DEBUG, INFO, WARN, ERROR, FATAL_ERROR)
+#project.debuglevel=ERROR
+
+# Max memory to allocate to Java
+#forrest.maxmemory=64m
+
+# Any other arguments to pass to the JVM. For example, to run on an X-less
+# server, set to -Djava.awt.headless=true
+#forrest.jvmargs=
+
+# The bugtracking URL - the issue number will be appended
+#project.bugtracking-url=http://issues.apache.org/bugzilla/show_bug.cgi?id=
+#project.bugtracking-url=http://issues.apache.org/jira/browse/
+
+# The issues list as rss
+#project.issues-rss-url=
+
+#I18n Property. Based on the locale request for the browser.
+#If you want to use it for static site then modify the JVM system.language
+# and run once per language
+#project.i18n=true
+
+# The names of plugins that are required to build the project
+# comma separated list (no spaces)
+# You can request a specific version by appending "-VERSION" to the end of
+# the plugin name. If you exclude a version number the latest released version
+# will be used, however, be aware that this may be a development version. In
+# a production environment it is recomended that you specify a known working
+# version.
+# Run "forrest available-plugins" for a list of plug-ins currently available
+project.required.plugins=org.apache.forrest.plugin.output.pdf
+
+# Proxy configuration
+# proxy.host=
+# proxy.port=
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../build/trailer.xml">
+]>
+
+<project name="crawler" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value=".."/>
+ <property name="module.name" value="wamblee-gps" />
+
+ &header;
+
+ <target name="module.build.deps"
+ depends="wamblee.support.d,dom4j.d,jfreechart.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="wamblee.support.test.d">
+
+ </target>
+
+
+ &trailer;
+
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+
+/**
+ * Represents a coordinate system.
+ */
+public interface CoordinateSystem extends Serializable {
+
+ /**
+ * Conversion to a reference coordinate system.
+ * @param aCoordinates Coordinates.
+ * @return Coordinates in the reference system.
+ */
+ Coordinates toReferenceSystem(Coordinates aCoordinates);
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+/**
+ * Coordinates in some 3-dimensional coordinate system.
+ */
+public class Coordinates implements Serializable {
+
+ private double _x1;
+ private double _x2;
+ private double _x3;
+
+ /**
+ * Constructs the coordinates.
+ * @param aX1 First coordinate.
+ * @param aX2 Second coordinate.
+ * @param aX3 Third coordinate.
+ */
+ public Coordinates(double aX1, double aX2, double aX3) {
+ _x1 = aX1;
+ _x2 = aX2;
+ _x3 = aX3;
+ }
+
+ public double getX1() {
+ return _x1;
+ }
+
+ public double getX2() {
+ return _x2;
+ }
+
+ public double getX3() {
+ return _x3;
+ }
+
+ public double getX(int i) {
+ switch (i) {
+ case 1: return _x1;
+ case 2: return _x2;
+ case 3: return _x3;
+ }
+ throw new IllegalArgumentException("coordinate out of range " + i);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "(" + getX1() + ", " + getX2() + ", " + getX3() + ")";
+ }
+
+ public Coordinates add(Coordinates aC) {
+ return new Coordinates(_x1 + aC._x1, _x2 + aC._x2, _x3 + aC._x3);
+ }
+
+ public Coordinates subtract(Coordinates aC) {
+ return new Coordinates(_x1 - aC._x1, _x2 - aC._x2, _x3 - aC._x3);
+ }
+
+ public double innerProduct(Coordinates aC) {
+ return _x1 * aC._x1 + _x2 * aC._x2 + _x3 * aC._x3;
+ }
+
+ public Coordinates outerProduct(Coordinates aC) {
+ return new Coordinates(
+ _x2*aC._x3 - _x3*aC._x2,
+ -_x1*aC._x3 + _x3*aC._x1,
+ _x1*aC._x2 - _x2*aC._x1
+ );
+ }
+
+ public double norm() {
+ return Math.sqrt(innerProduct(this));
+ }
+
+ public Coordinates scale(double aMultiplier) {
+ return new Coordinates(_x1*aMultiplier, _x2*aMultiplier, _x3*aMultiplier);
+ }
+
+ public Coordinates normalize() {
+ return scale(1.0/norm());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import org.wamblee.general.Pair;
+
+/**
+ * Represents a plane. Usually used to represent a tangent plane to the earth surface to
+ * locally approximate the earth as flat.
+ */
+public class Plane {
+
+ private static final double EPS = 1e-4;
+
+ private Coordinates _point;
+ private Coordinates _normal;
+ private Coordinates _north;
+ private Coordinates _east;
+
+ /**
+ * Constructs a plane.
+ * @param aPoint Point on the plane.
+ * @param aNormal Normal, not necessarily normalized.
+ */
+ public Plane(Point aPoint, Point aNormal) {
+ _point = aPoint.getReferenceCoordinates();
+ _normal = aNormal.getReferenceCoordinates().normalize();
+ Coordinates north = new Coordinates(0.0, 0.0, 1.0);
+ _north = north.subtract(_normal.scale(north.innerProduct(_normal))).normalize();
+ _east = _north.outerProduct(_normal);
+
+ if ( _normal.innerProduct(_north) > EPS ) {
+ throw new IllegalArgumentException("North access is not within the plane");
+ }
+ }
+
+ /**
+ * Projects a point onto the plane.
+ * @param aPoint Point to project.
+ * @return Projected point.
+ */
+ private Coordinates project(Point aPoint) {
+ Coordinates ref = aPoint.getReferenceCoordinates();
+ double lambda = _normal.innerProduct(
+ _point.subtract(ref));
+ return ref.add(_normal.scale(lambda));
+ }
+
+ /**
+ * Returns normalized coordinates within the plane of the projection of a point.
+ */
+ public Pair<Double,Double> normalizedProjection(Point aPoint) {
+ Coordinates projection = project(aPoint);
+ Coordinates delta = projection.subtract(_point);
+ double x1 = delta.innerProduct(_north);
+ double x2 = delta.innerProduct(_east);
+ return new Pair<Double,Double>(x1, x2);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+
+/**
+ * Represents a point in some coordinate system.
+ */
+public class Point implements Serializable {
+
+ private Coordinates _coordinates;
+ private CoordinateSystem _system;
+
+ /**
+ * Constructs the point.
+ * @param aCoordinates Coordinates of the point in its coordinate system.
+ * @param aSystem Coordinate system.
+ */
+ public Point(Coordinates aCoordinates, CoordinateSystem aSystem) {
+ _coordinates = aCoordinates;
+ _system = aSystem;
+ }
+
+ /**
+ * Gets the coordinates in the point's coordinate system.
+ * @return Coordinates.
+ */
+ public Coordinates getCoordinates() {
+ return _coordinates;
+ }
+
+ public Coordinates getReferenceCoordinates() {
+ return _system.toReferenceSystem(_coordinates);
+ }
+
+ /**
+ * Gets the coordinate system.
+ * @return Coordinate system.
+ */
+ public CoordinateSystem getCoordinateSystem() {
+ return _system;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getCoordinates().toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+
+/**
+ * Reference coordinate system which is the basis for defining metrics.
+ * This is a Cartesian coordinate system.
+ */
+public class ReferenceCoordinateSystem implements CoordinateSystem {
+
+ /**
+ * Constructs the coordinate system.
+ *
+ */
+ public ReferenceCoordinateSystem() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ return aCoordinates;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.gpx.CoordinateSystem#distance(org.wamblee.gpx.Coordinates,
+ * org.wamblee.gpx.Coordinates)
+ */
+ private static double distance(Coordinates aC1, Coordinates aC2) {
+ return Math.sqrt(square(aC1.getX1() - aC2.getX1())
+ + square(aC1.getX2() - aC2.getX2())
+ + square(aC1.getX3() - aC2.getX3()));
+ }
+
+ private static double square(double x) {
+ return x * x;
+ }
+
+ /**
+ * Computes the distance between two points in arbitrary coordinate systems.
+ * @param aP1 First point.
+ * @param aP2 Second point.
+ * @return Distance.
+ */
+ public static double distance(Point aP1, Point aP2) {
+ return distance( aP1.getCoordinateSystem().toReferenceSystem(aP1.getCoordinates()),
+ aP2.getCoordinateSystem().toReferenceSystem(aP2.getCoordinates()));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import static java.lang.Math.PI;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+
+
+/**
+ * Represents the coordinate system for a GPS measurement identified by
+ * <ul>
+ * <li> x1: latitude in degrees </li>
+ * <li> x2: longitude in degrees </li>
+ * <li> x3: elevation in meters </li>
+ * </ul>
+ * This coordinate system models the earth as a sphere of a specific radius.
+ */
+public class SphericalCoordinateSystem implements CoordinateSystem {
+ /**
+ * Earth radius in meters.
+ */
+ private static final double EARTH_RADIUS = 6371000;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ double latrad = radians(aCoordinates.getX1());
+ double lonrad = radians(aCoordinates.getX2());
+ double coslat = cos(latrad);
+ double sinlat = sin(latrad);
+ double coslon = cos(lonrad);
+ double sinlon = sin(lonrad);
+
+ double trueElevation = EARTH_RADIUS + aCoordinates.getX3();
+ return new Coordinates(trueElevation*coslat*coslon,
+ trueElevation*coslat*sinlon,
+ trueElevation*sinlat);
+
+ }
+
+ private double radians(double aDegrees) {
+ return aDegrees/180.0*PI;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import static java.lang.Math.PI;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+
+
+/**
+ * Represents the WGS 84 coordinate system for a GPS measurement identified by
+ * <ul>
+ * <li> x1: latitude in degrees </li>
+ * <li> x2: longitude in degrees </li>
+ * <li> x3: elevation in meters </li>
+ * </ul>
+ * WGS84 models the earth as an ellipse.
+ */
+public class Wgs84CoordinateSystem implements CoordinateSystem {
+ /*
+ * Ellipsoide parameters, where the ellipsoide is defined by
+ *
+ * (x^2 + y^2)/a^2 + z^2/b^2 = 1
+ */
+
+ /**
+ * The radius of the ellipse at the equator
+ */
+ private static final double A = 6378137.000;
+
+ /**
+ * The distance of the North and South poles to the center of the ellipsoide.
+ */
+ private static final double B = 6356752.314;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ double latrad = radians(aCoordinates.getX1());
+ double lonrad = radians(aCoordinates.getX2());
+ double coslat = cos(latrad);
+ double sinlat = sin(latrad);
+ double coslon = cos(lonrad);
+ double sinlon = sin(lonrad);
+
+ double r = A*B/Math.sqrt(B*B*coslat*coslat + A*A*sinlat*sinlat) + aCoordinates.getX3();
+
+ return new Coordinates(r*coslat*coslon,
+ r*coslat*sinlon,
+ r*sinlat);
+
+ }
+
+ private double radians(double aDegrees) {
+ return aDegrees/180.0*PI;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.track;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.gps.geometry.Point;
+
+/**
+ * Represents a GPS track.
+ */
+public class Track implements Serializable {
+
+ private List<TrackPoint> _points;
+
+ /**
+ * Constructs an empty track.
+ *
+ */
+ public Track() {
+ _points = new ArrayList<TrackPoint>();
+ }
+
+ /**
+ * Adds a point to a track.
+ * @param aPoint Point.
+ */
+ public void addPoint(TrackPoint aPoint) {
+ _points.add(aPoint);
+ }
+
+ /**
+ * @return Number of points in the track.
+ */
+ public int size() {
+ return _points.size();
+ }
+
+ public double getMinCoordinate(int i) {
+ if ( size() == 0 ) {
+ throw new IllegalArgumentException("empty track");
+ }
+ double min = getPoint(0).getCoordinates().getX(i);
+ for (int j = 1; j < size(); j++) {
+ min = Math.min(min, getPoint(j).getCoordinates().getX(i));
+ }
+ return min;
+ }
+
+ public double getMaxCoordinate(int i) {
+ if ( size() == 0 ) {
+ throw new IllegalArgumentException("empty track");
+ }
+ double max = getPoint(0).getCoordinates().getX(i);
+ for (int j = 1; j < size(); j++) {
+ max = Math.max(max, getPoint(j).getCoordinates().getX(i));
+ }
+ return max;
+ }
+
+ /**
+ * Gets the point at the given inded.
+ * @param aIndex 0 <= aIndex < size()
+ * @return Point.
+ */
+ public Point getPoint(int aIndex) {
+ return _points.get(aIndex);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.track;
+
+import org.wamblee.gps.geometry.Coordinates;
+import org.wamblee.gps.geometry.Point;
+import org.wamblee.gps.geometry.Wgs84CoordinateSystem;
+
+
+/**
+ * A point from a GPS track.
+ *
+ * TODO should be extended with additional information (e.g. date/time if available).
+ */
+public class TrackPoint extends Point {
+
+ /**
+ * Constructs the point.
+ * @param aLatitude Latitude in degrees.
+ * @param aLongitude Longitude in degrees.
+ * @param aElevation Elevation in metres.
+ */
+ public TrackPoint(double aLatitude, double aLongitude, double aElevation) {
+ super(new Coordinates(aLatitude, aLongitude, aElevation), new Wgs84CoordinateSystem());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.wamblee.gps.track.Track;
+import org.wamblee.gps.track.TrackPoint;
+import org.wamblee.xml.DomUtils;
+import org.wamblee.xml.XMLException;
+
+/**
+ * Parser for GPX tracks.
+ */
+public class GpxParser {
+
+ private static final String SCHEMA_RESOURCE = "gpx.xsd";
+
+ public GpxParser() {
+ // Empty.
+ }
+
+ public Track parse(InputStream aIs) throws XMLException {
+ Document doc = DomUtils.convert(DomUtils.read(aIs));
+ return parse(doc);
+ }
+
+ /**
+ * @param doc
+ */
+ public Track parse(Document doc) {
+ Track track = new Track();
+ Element root = doc.getRootElement().element("trk").element("trkseg");
+ for ( Iterator i =root.elementIterator("trkpt"); i.hasNext(); ) {
+ Element trkpt = (Element)i.next();
+ track.addPoint(parseTrackPoint(trkpt));
+ }
+ return track;
+ }
+
+ /**
+ * @param trkpt
+ */
+ private TrackPoint parseTrackPoint(Element trkpt) {
+ //System.out.println(trkpt.asXML() + "|\n");
+ double latitude = new Double(trkpt.attributeValue("lat"));
+ double longitude = new Double(trkpt.attributeValue("lon"));
+ Element ele = trkpt.element("ele");
+ double elevation = 0.0;
+ if ( ele != null ) {
+ elevation = new Double(ele.getText());
+ }
+ //System.out.println(" lat = " + lat + " lon = " + lon + " ele = " + ele);
+ return new TrackPoint(latitude, longitude, elevation);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.awt.Color;
+import java.awt.Image;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartFrame;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.wamblee.general.Pair;
+import org.wamblee.gps.geometry.Plane;
+import org.wamblee.gps.geometry.Point;
+import org.wamblee.gps.geometry.ReferenceCoordinateSystem;
+import org.wamblee.gps.track.Track;
+import org.wamblee.utils.JpegUtils;
+
+/**
+ * Parses a GPX file and prints out a data file with each trackpoints distance from the start of the
+ * track and its elevation, separated 0by a space.
+ */
+public class GpxPlotter {
+
+ public static void main(String[] aArgs) throws Exception {
+ File file = new File(aArgs[0]);
+ GpxParser parser = new GpxParser();
+ Track track = parser.parse(new FileInputStream(file));
+
+ List<Pair<Double,Double>> elevationProfile = computeElevationProfile(track);
+ printTrack(elevationProfile);
+ computeTotalClimb(elevationProfile);
+ plotElevationProfile(elevationProfile);
+ List<Pair<Double,Double>> trackXy = computeTrackXY(track);
+ List<Pair<Double,Double>> trackLatLon = computeTrackLatLon(track);
+ plotTrack(trackLatLon);
+ }
+
+ private static List<Pair<Double, Double>> computeElevationProfile(Track aTrack) {
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ double distance = 0.0;
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ results.add(new Pair<Double,Double>(distance, point.getCoordinates().getX3()));
+ if ( i+1 < aTrack.size()) {
+ Point nextPoint = aTrack.getPoint(i+1);
+ distance += ReferenceCoordinateSystem.distance(point, nextPoint);
+ }
+ }
+ return results;
+ }
+
+ private static List<Pair<Double, Double>> computeTrackXY(Track aTrack) {
+ Point reference = aTrack.getPoint(0);
+ Plane plane = new Plane(reference, reference); // assume the earth is spherical.
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ Pair<Double,Double> projection = plane.normalizedProjection(point);
+ results.add(projection);
+ System.out.println(point);
+ }
+ return results;
+ }
+
+ private static List<Pair<Double, Double>> computeTrackLatLon(Track aTrack) {
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ results.add(new Pair<Double,Double>(point.getCoordinates().getX1(), point.getCoordinates().getX2()));
+ }
+ return results;
+ }
+
+
+
+ private static void printTrack(List<Pair<Double,Double>> aHeightProfile) {
+ for (Pair<Double,Double> point: aHeightProfile) {
+ System.out.println(point.getFirst() + " " + point.getSecond());
+ }
+ }
+
+ private static void computeTotalClimb(List<Pair<Double,Double>> aHeightProfile) {
+ double result = 0.0;
+
+ double lastHeight = aHeightProfile.get(0).getSecond();
+ for ( int i = 1; i < aHeightProfile.size(); i++) {
+ double height = aHeightProfile.get(i).getSecond();
+ if ( height > lastHeight) {
+ result += (height-lastHeight);
+ }
+ lastHeight = height;
+ }
+ System.out.println("Total climb: " + result);
+ }
+
+ private static void plotElevationProfile(List<Pair<Double,Double>> aHeightProfile) throws IOException {
+ XYSeriesCollection dataset = createDataset(aHeightProfile, "height");
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Height Profile",
+ "Distance(m)",
+ "Height(m)",
+ dataset,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false);
+ ChartUtilities.writeChartAsPNG(new FileOutputStream("height.png"), chart, 600, 300);
+ ChartFrame frame = new ChartFrame("test", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ private static void plotTrack(List<Pair<Double,Double>> aPoints) throws IOException, InterruptedException {
+ XYSeriesCollection dataset = createDataset(aPoints, "track");
+ JFreeChart chart = createLineChart(dataset);
+
+ Pair<Pair<Double,Double>,Pair<Double,Double>> bounds = getBounds(aPoints);
+
+ chart.getXYPlot().getDomainAxis().setLowerBound(bounds.getFirst().getFirst());
+ chart.getXYPlot().getDomainAxis().setUpperBound(bounds.getFirst().getSecond());
+
+ chart.getXYPlot().getRangeAxis().setLowerBound(bounds.getSecond().getFirst());
+ chart.getXYPlot().getRangeAxis().setUpperBound(bounds.getSecond().getSecond());
+
+ Image background = JpegUtils.loadJpegImage(new FileInputStream("/home/erik/vakantie.jpg"));
+ chart.getPlot().setBackgroundImage(background);
+
+ XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer)chart.getXYPlot().getRenderer();
+ renderer.setShapesVisible(true);
+ renderer.setShapesFilled(true);
+ renderer.setPaint(Color.BLACK);
+
+ ChartUtilities.writeChartAsPNG(new FileOutputStream("test.png"), chart, 1280, 800);
+ ChartFrame frame = new ChartFrame("test", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ /**
+ * @param dataset
+ * @return
+ */
+ private static JFreeChart createLineChart(XYSeriesCollection dataset) {
+ NumberAxis xAxis = new NumberAxis("S->N");
+ xAxis.setAutoRangeIncludesZero(false);
+
+ NumberAxis yAxis = new NumberAxis("W->E");
+ yAxis.setAutoRangeIncludesZero(false);
+
+ XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
+ XYPlot plot = new ZoomableBackgroundXYPlot(dataset, xAxis, yAxis, renderer);
+ plot.setOrientation(PlotOrientation.HORIZONTAL);
+
+ JFreeChart chart = new JFreeChart(
+ "Track", JFreeChart.DEFAULT_TITLE_FONT, plot, true
+ );
+
+ return chart;
+ /*
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Track",
+ "S->N",
+ "W->E",
+ dataset,
+ PlotOrientation.HORIZONTAL,
+ true,
+ true,
+ false);
+ return chart;
+ */
+ }
+
+ /**
+ * @param aHeightProfile
+ * @return
+ */
+ private static XYSeriesCollection createDataset(List<Pair<Double, Double>> aHeightProfile, String aName) {
+ XYSeries series = new XYSeries(aName, false);
+ for (Pair<Double,Double> point: aHeightProfile) {
+ series.add(point.getFirst(), point.getSecond());
+ }
+ XYSeriesCollection dataset = new XYSeriesCollection(series);
+ return dataset;
+ }
+
+ private static Pair<Pair<Double,Double>,Pair<Double,Double>> getBounds(List<Pair<Double,Double>> aList) {
+ Pair<Double,Double> first = aList.get(0);
+ double minx= first.getFirst();
+ double maxx = minx;
+ double miny = first.getSecond();
+ double maxy = miny;
+
+ for (int i = 0; i < aList.size(); i++) {
+ Pair<Double,Double> value = aList.get(i);
+ minx = Math.min(minx, value.getFirst());
+ maxx = Math.max(maxx, value.getFirst());
+ miny = Math.min(miny, value.getSecond());
+ maxy = Math.max(maxy, value.getSecond());
+ }
+ if ( maxx == minx ) {
+ maxx += 1.0; // to avoid problems.
+ }
+ if ( maxy == miny ) {
+ maxy += 1.0; // to avoid problems.
+ }
+ final double paddingFactor = 0.3; // allow some space around min and max
+ return new Pair<Pair<Double,Double>,Pair<Double,Double>>(
+ new Pair<Double,Double>( minx - paddingFactor*(maxx-minx),
+ maxx + paddingFactor*(maxx-minx)),
+ new Pair<Double,Double>( miny - paddingFactor*(maxy-miny),
+ maxy + paddingFactor*(maxy-miny))
+ );
+ }
+}
+
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.data.xy.XYDataset;
+
+/**
+ * Extension of XY plot that provides automatic zooming of the background
+ * image.
+ */
+public class ZoomableBackgroundXYPlot extends XYPlot {
+
+ /*
+ * Initial domain axis.
+ */
+ private double _x1 = 1.0;
+ private double _x2 = -1.0; // _x2 < _x1 initially to make signify uninitialized values.
+
+ /*
+ * Initial range axis.
+ */
+ private double _y1 = 1.0;
+ private double _y2 = -1.0; // _y2 < _y1 initially to make signify uninitialized values.
+
+ public ZoomableBackgroundXYPlot(XYDataset aDataset,
+ ValueAxis aDomainAxis, ValueAxis aRangeAxis, XYItemRenderer aRenderer) {
+ super(aDataset, aDomainAxis, aRangeAxis, aRenderer);
+ }
+
+ /* (non-Javadoc)
+ * @see org.jfree.chart.plot.Plot#drawBackgroundImage(java.awt.Graphics2D, java.awt.geom.Rectangle2D)
+ */
+ @Override
+ protected void drawBackgroundImage(Graphics2D g2, Rectangle2D area) {
+ //System.out.println("--------");
+ //System.out.println("Area: " + area);
+ //System.out.println("Graphics clip: " + g2.getClipBounds());
+ //System.out.println("Domain axis: " + getDomainAxis().getLowerBound() + " " +
+ // getDomainAxis().getUpperBound());
+
+ // Get the current domain axis bounds
+ double y1 = getDomainAxis().getLowerBound();
+ double y2 = getDomainAxis().getUpperBound();
+ double x1 = getRangeAxis().getLowerBound();
+ double x2 = getRangeAxis().getUpperBound();
+
+ if ( _x2 < _x1 ) {
+ // initial domain axis bounds
+ _y1 = y1;
+ _y2 = y2;
+ _x1 = x1;
+ _x2 = x2;
+ }
+
+ Image background = getBackgroundImage();
+ int width = background.getWidth(null);
+ int height = background.getHeight(null);
+
+ // Determine the part of the image to be drawn on the screen based on the scaling
+ // of the domain axes.
+ int imageX1 = (int)Math.round(1 + (x1 - _x1)*(width-1)/(_x2 - _x1));
+ int imageX2 = (int)Math.round(1 + (x2 - _x1)*(width-1)/(_x2 - _x1));
+ // Note: y-axis of image goes from bottom to top.
+ int imageY2 = (int)Math.round(height + (y1 - _y1)*(1-height)/(_y2 - _y1));
+ int imageY1 = (int)Math.round(height + (y2 - _y1)*(1-height)/(_y2 - _y1));
+
+ // Draw the correct part of the image on the screen.
+ g2.drawImage(background, (int)area.getMinX(), (int)area.getMinY(), (int)area.getMaxX(), (int)area.getMaxY(),
+ imageX1, imageY1, imageX2, imageY2, null);
+
+ // System.out.println("========");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<xsd:schema\r
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
+ xmlns="http://www.topografix.com/GPX/1/1"\r
+ targetNamespace="http://www.topografix.com/GPX/1/1"\r
+ elementFormDefault="qualified">\r
+\r
+<xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp\r
+\r
+ GPX uses the following conventions: all coordinates are relative to the WGS84 datum. All measurements are in metric units.\r
+ </xsd:documentation>\r
+</xsd:annotation>\r
+\r
+ <xsd:element name="gpx" type="gpxType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX is the root element in the XML file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:complexType name="gpxType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements\r
+ to the extensions section of the GPX document.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="metadata" type="metadataType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Metadata about the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="wpt" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of waypoints.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="rte" type="rteType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of routes.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="trk" type="trkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of tracks.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+\r
+ <xsd:attribute name="version" type="xsd:string" use="required" fixed="1.1">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You must include the version number in your GPX document.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="creator" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You must include the name or URL of the software that created your GPX document. This allows others to\r
+ inform the creator of a GPX instance document that fails to validate.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="metadataType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Information about the GPX file, author, and copyright restrictions goes in the metadata section. Providing rich,\r
+ meaningful information about your GPX files allows others to search for and use your GPS data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The name of the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A description of the contents of the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="author" type="personType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The person or organization who created the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="copyright" type="copyrightType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Copyright and license information governing use of the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ URLs associated with the location described in the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The creation date of the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="keywords" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Keywords associated with the file. Search engines or databases can use this information to classify the data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="bounds" type="boundsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Minimum and maximum coordinates which describe the extent of the coordinates in the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="wptType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ wpt represents a waypoint, point of interest, or named feature on a map.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <!-- Position info -->\r
+ <xsd:element name="ele" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Elevation (in meters) of the point.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs. \r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="magvar" type="degreesType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Magnetic variation (in degrees) at the point\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="geoidheight" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <!-- Description info -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS waypoint comment. Sent to GPS as comment. \r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A text description of the element. Holds additional information about the element intended for the user, not the GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to additional information about the waypoint.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="sym" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of the waypoint.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <!-- Accuracy info -->\r
+ <xsd:element name="fix" type="fixType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type of GPX fix.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="sat" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Number of satellites used to calculate the GPX fix.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="hdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Horizontal dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="vdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Vertical dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="pdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Position dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="ageofdgpsdata" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Number of seconds since last DGPS update.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="dgpsid" type="dgpsStationType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ ID of DGPS station used in differential correction.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+\r
+ <xsd:attribute name="lat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="lon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="rteType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS name of route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS comment for route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text description of route for user. Not sent to GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Links to external information about the route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS route number.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ \r
+ <xsd:element name="rtept" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of route points.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="trkType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ trk represents a track - an ordered list of points describing a path.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS name of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS comment for track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ User description of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Links to external information about track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS track number.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ \r
+ <xsd:element name="trkseg" type="trksegType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+ \r
+ <xsd:complexType name="extensionsType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:any>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="trksegType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="trkpt" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="copyrightType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Information about the copyright holder and any license governing use of this file. By linking to an appropriate license,\r
+ you may place your data into the public domain or grant additional usage rights.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="year" type="xsd:gYear" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Year of copyright.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="license" type="xsd:anyURI" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to external file containing license text.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="author" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Copyright holder (TopoSoft, Inc.)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="linkType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A link to an external resource (Web page, digital photo, video clip, etc) with additional information.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="text" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text of hyperlink.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Mime type of content (image/jpeg)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="href" type="xsd:anyURI" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ URL of hyperlink.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="emailType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ An email address. Broken into two parts (id and domain) to help prevent email harvesting.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:attribute name="id" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ id half of email address (billgates2004)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="domain" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ domain half of email address (hotmail.com)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="personType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A person or organization.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Name of person or organization.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="email" type="emailType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Email address.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to Web site or other external information about person.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="ptType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A geographic point with optional elevation and time. Available for use by other schemas.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="ele" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The elevation (in meters) of the point.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The time that the point was recorded.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="lat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="lon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="ptsegType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ An ordered sequence of points. (for polygons or polylines, e.g.)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="pt" type="ptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Ordered list of geographic points.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="boundsType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Two lat/lon pairs defining the extent of an element.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:attribute name="minlat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The minimum latitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="minlon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The minimum longitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="maxlat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The maximum latitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="maxlon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The maximum longitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+\r
+ <xsd:simpleType name="latitudeType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="-90.0"/>\r
+ <xsd:maxInclusive value="90.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="longitudeType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The longitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="-180.0"/>\r
+ <xsd:maxExclusive value="180.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="degreesType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Used for bearing, heading, course. Units are decimal degrees, true (not magnetic).\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="0.0"/>\r
+ <xsd:maxExclusive value="360.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="fixType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="none"/>\r
+ <xsd:enumeration value="2d"/>\r
+ <xsd:enumeration value="3d"/>\r
+ <xsd:enumeration value="dgps"/>\r
+ <xsd:enumeration value="pps"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="dgpsStationType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Represents a differential GPS station.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:integer">\r
+ <xsd:minInclusive value="0"/>\r
+ <xsd:maxInclusive value="1023"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+</xsd:schema>\r
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.utils;
+
+import java.awt.Container;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.sun.image.codec.jpeg.JPEGCodec;
+import com.sun.image.codec.jpeg.JPEGEncodeParam;
+import com.sun.image.codec.jpeg.JPEGImageDecoder;
+import com.sun.image.codec.jpeg.JPEGImageEncoder;
+
+/**
+ * Utility functions for processing JPEG images.
+ */
+public class JpegUtils {
+ /**
+ * Scales an image preserving the aspect ratio.
+ *
+ * @param aMaxWidth Maximum width.
+ * @param aMaxHeight Maximum height.
+ * @param aImage Image to scale.
+ * @return Scaled image.
+ */
+ public static BufferedImage scaleImage(int aMaxWidth, int aMaxHeight, Image aImage) {
+ double thumbRatio = (double) aMaxWidth / (double) aMaxHeight;
+ int imageWidth = aImage.getWidth(null);
+ int imageHeight = aImage.getHeight(null);
+ double imageRatio = (double) imageWidth / (double) imageHeight;
+ if (thumbRatio < imageRatio) {
+ aMaxHeight = (int) (aMaxWidth / imageRatio);
+ } else {
+ aMaxWidth = (int) (aMaxHeight * imageRatio);
+ }
+ BufferedImage thumbImage = new BufferedImage(aMaxWidth, aMaxHeight,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics2D = thumbImage.createGraphics();
+ graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ graphics2D.drawImage(aImage, 0, 0, aMaxWidth, aMaxHeight, null);
+ return thumbImage;
+ }
+
+ /**
+ * Loads a jpeg image from an input stream.
+ *
+ * @param aInput Input stream.
+ * @return JPEG image.
+ * @throws IOException In case of IO problems.
+ * @throws InterruptedException When execution is interrupted.
+ */
+ public static BufferedImage loadJpegImage(InputStream aInput) throws IOException, InterruptedException {
+ JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(aInput);
+ BufferedImage image = decoder.decodeAsBufferedImage();
+ MediaTracker mediaTracker = new MediaTracker(new Container());
+ mediaTracker.addImage(image, 0);
+ mediaTracker.waitForID(0);
+ return image;
+ }
+
+ /**
+ * Writes a JPEG image.
+ *
+ * @param aOutput Output stream to write to.
+ * @param aQuality Quality of the JPEG image in the range 0..100
+ * @param aThumbImage
+ * @throws IOException
+ */
+ public static void writeJpegImage(OutputStream aOutput, int aQuality, BufferedImage aThumbImage) throws IOException {
+ // save thumbnail image to OUTFILE
+
+ if ( aQuality < 0 || aQuality > 100 ) {
+ throw new IllegalArgumentException("Argument quality must be in range 0.100: " + aQuality);
+ }
+
+ JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(aOutput);
+ JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(aThumbImage);
+ aQuality = Math.max(0, Math.min(aQuality, 100));
+ param.setQuality((float) aQuality / 100.0f, false);
+ encoder.setJPEGEncodeParam(param);
+ encoder.encode(aThumbImage);
+ }
+
+}
+
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../build/trailer.xml">
+]>
+
+<project name="socketproxy" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value=".."/>
+ <property name="module.name" value="wamblee-socketproxy" />
+
+ &header;
+
+ <target name="module.build.deps"
+ depends="">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.socketproxy;
+
+public class Barrier {
+
+ private int _countLeft;
+
+ public Barrier( int aCount ) {
+ _countLeft = aCount;
+ }
+
+ public synchronized void block( ) {
+ _countLeft--;
+ if ( _countLeft < 0 ) {
+ throw new IllegalStateException(
+ "Barrier count became negative, programming error" );
+ }
+ notifyAll( );
+ while ( _countLeft > 0 ) {
+ waitUninterruptable( );
+ }
+ }
+
+ private void waitUninterruptable( ) {
+ try {
+ wait( );
+ } catch ( InterruptedException e ) {
+ // ignore.
+ }
+ }
+
+}
--- /dev/null
+package org.wamblee.socketproxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Forwarder thread which handles forwarding of an input stream to
+ * an output stream.
+ */
+
+public class ForwarderThread extends Thread {
+
+ private String _prefix;
+
+ private Barrier _barrier;
+
+ private InputStream _is;
+
+ private OutputStream _os;
+
+ /**
+ * Constructs the forwarder thread.
+ * @param aPrefix Prefix to use in the output.
+ * @param aBarrier Barrier to block on before actually closing the stream.
+ * This is done to make sure that connections are only closed in th e
+ * proxy when the forwarders in both directions are ready to close.
+ * @param aIs Input stream to read from.
+ * @param aOs Output stream to forward to.
+ */
+ public ForwarderThread( String aPrefix, Barrier aBarrier,
+ InputStream aIs, OutputStream aOs ) {
+ _prefix = aPrefix;
+ _is = aIs;
+ _os = aOs;
+ _barrier = aBarrier;
+ }
+
+ public void run( ) {
+ boolean firstChar = true;
+ try {
+ int c = _is.read( );
+ while ( c > 0 ) {
+ try {
+ _os.write( c );
+ _os.flush( );
+ if ( firstChar ) {
+ System.out.print( _prefix );
+ firstChar = false;
+ }
+ System.out.print( (char) c );
+ if ( c == '\n' ) {
+ firstChar = true;
+ }
+ } catch ( IOException e ) {
+ }
+
+ c = _is.read( );
+ }
+ } catch ( IOException e ) {
+ }
+ closeStreams();
+ }
+
+ /**
+ * @param is
+ * @param os
+ */
+ private void closeStreams( ) {
+ _barrier.block( ); // wait until the other forwarder for the other direction
+ // is also closed.
+ try {
+ _is.close( );
+ } catch ( IOException e1 ) {
+ // Empty.
+ }
+ try {
+ _os.flush( );
+ _os.close( );
+ } catch ( IOException e1 ) {
+ // Empty
+ }
+ System.out.println(_prefix + " closed");
+ }
+
+}
--- /dev/null
+package org.wamblee.socketproxy;
+
+/*
+ * Created on Apr 5, 2005
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * @author erik
+ *
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class SocketProxy {
+
+ public static void main( final String[] args ) throws IOException {
+ for ( int i = 0; i < args.length; i++ ) {
+ // System.out.println(i + " " + args[i]);
+ String[] fields = args[i].split( ":" );
+ final int localPort = Integer.parseInt( fields[0] );
+ final String host = fields[1];
+ final int remotePort = Integer.parseInt( fields[2] );
+ runSocketProxy( localPort, host, remotePort );
+ }
+ }
+
+ /**
+ * @param localPort
+ * @param host
+ * @param remotePort
+ */
+ private static void runSocketProxy( final int localPort,
+ final String host, final int remotePort ) {
+ new Thread( new Runnable( ) {
+ public void run( ) {
+ try {
+ new SocketProxy( localPort, host, remotePort );
+ } catch ( IOException e ) {
+ System.out.println( "Problem with socket " + localPort
+ + ":" + host + ":" + remotePort );
+ e.printStackTrace( );
+ }
+ }
+ } ).start( );
+ }
+
+ public SocketProxy( int localPort, String remoteHost, int remotePort )
+ throws IOException {
+ System.out.println( "Listening on port " + localPort );
+ ServerSocket server = new ServerSocket( localPort );
+ for ( ;; ) {
+ Socket socket = server.accept( );
+ System.out.println( "Got local connection on port "
+ + localPort );
+ InputStream localIs = socket.getInputStream( );
+ OutputStream localOs = socket.getOutputStream( );
+ Socket clientSocket = new Socket( remoteHost, remotePort );
+ final String description = "Port forwarding: " + localPort
+ + " -> " + remoteHost + ":" + remotePort;
+ System.out.println( description + " established." );
+ InputStream serverIs = clientSocket.getInputStream( );
+ OutputStream serverOs = clientSocket.getOutputStream( );
+ Barrier barrier = new Barrier(2);
+ final Thread t1 = runForwarder( barrier, "> ", localIs, serverOs );
+ final Thread t2 = runForwarder( barrier, "< ", serverIs, localOs );
+ waitForConnectionClose( description, t1, t2 );
+ }
+ }
+
+ /**
+ * @param description
+ * @param t1
+ * @param t2
+ */
+ private void waitForConnectionClose( final String description,
+ final Thread t1, final Thread t2 ) {
+ new Thread( new Runnable( ) {
+ public void run( ) {
+ try {
+ t1.join( );
+ t2.join( );
+ } catch ( InterruptedException e ) {
+ e.printStackTrace( );
+ }
+ System.out.println( description + " closed" );
+ }
+ } ).start( );
+ }
+
+ private Thread runForwarder( final Barrier barrier, final String prefix,
+ final InputStream is, final OutputStream os ) {
+ Thread t = new ForwarderThread(prefix, barrier, is, os);
+ t.start( );
+ return t;
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+
+<!DOCTYPE project [
+ <!ENTITY header SYSTEM "file:../build/header.xml">
+ <!ENTITY trailer SYSTEM "file:../build/trailer.xml">
+]>
+
+<project name="support" default="jar" basedir=".">
+
+
+ <!-- =============================================================================== -->
+ <!-- Include the build header defining general properties -->
+ <!-- =============================================================================== -->
+ <property name="project.home" value=".."/>
+ <property name="module.name" value="wamblee-support" />
+
+ &header;
+
+ <target name="module.build.deps"
+ depends="logging.d,commons-collections.d,commons-beanutils.d,dom4j.d,xerces.d,ehcache.d,spring.d">
+ </target>
+
+ <!-- Set libraries to use in addition for test, a library which
+ is already mentioned in module.build.path should not be
+ mentioned below again -->
+ <target name="module.test.deps" depends="hibernate.standalone.d">
+ </target>
+
+ &trailer;
+
+
+</project>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=WARN
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="test"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="test"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org/wamblee/general/spring1.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactoryBla
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactory
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <bean id="java.lang.String"
+ class="java.lang.String">
+ <constructor-arg><value>hello</value></constructor-arg>
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+This is my resource
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <h2>Successfully recorded programs <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:25 - 00:10: <strong>Wintertijd</strong> (Nederland
+ 1/Documentaire)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Some description MINSK - De presidentsverkiezingen
+ in Wit-Rusland zijn zondag met ruime cijfers gewonnen door
+ zittend president Aleksandr Loekasjenko. Dat bleek zondag uit
+ exitpolls uitgevoerd in opdracht van het totalitaire regime. Het
+ staatshoofd zou kunnen rekenen op ruim 82 procent van de
+ stemmen. Volgens de eerste gedeeltelijke uitslagen zou
+ Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p></h2>
+ <h2>Possibly interesting programs</h2>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Brainiac</strong> (Discovery Channel/science)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Humor</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ <h3>Category: horror</h3>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Andere tijden</strong> (Nederland 1/docu)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Documentaire</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ </body>
+</html>
--- /dev/null
+<report>
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifdd test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:ifdd>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifx test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:include href="org/wamblee/xml/utilities.xsl"/>
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:template match="report">
+ <xsl:text>Hello world!</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * The <code>Cache</code> interface represents... a cache.
+ * In some circumstances it is more optimal to implement caching directly in
+ * the code instead of relying on Hibernate caching methods. This interface abstracts
+ * from the used cache implementation.
+ * Cache implementations must be thread-safe.
+ */
+public interface Cache<KeyType extends Serializable, ValueType extends Serializable> {
+
+ /**
+ * Adds a key-value pair to the cache.
+ * @param aKey Key.
+ * @param aValue Value.
+ */
+ void put(KeyType aKey, ValueType aValue);
+
+ /**
+ * Retrieves a value from the cache.
+ * @param aKey Key to retrieve.
+ * @return Key.
+ */
+ ValueType get(KeyType aKey);
+
+ /**
+ * Removes an entry from the cache.
+ * @param aKey Key to remove the entry for.
+ */
+ void remove(KeyType aKey);
+
+ /**
+ * Removes all entries from the cache.
+ *
+ */
+ void clear();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Represents a cached object. The object is either retrieved from the cache if
+ * the cache has it, or a call back is invoked to get the object (and put it in
+ * the cache).
+ */
+public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
+
+ private static final Logger LOGGER = Logger.getLogger(CachedObject.class);
+
+ /**
+ * Callback invoked to compute an object if it was not found in the cache.
+ *
+ * @param <T>
+ * Type of the object
+ */
+ public static interface Computation<Key extends Serializable, Value extends Serializable> {
+ /**
+ * Gets the object. Called when the object is not in the cache.
+ *
+ * @param aObjectKey
+ * Id of the object in the cache.
+ * @return Object, must be non-null.
+ */
+ Value getObject(Key aObjectKey);
+ }
+
+ /**
+ * Cache to use.
+ */
+ private Cache<KeyType, ValueType> _cache;
+
+ /**
+ * Key of the object in the cache.
+ */
+ private KeyType _objectKey;
+
+ /**
+ * Computation used to obtain the object if it is not found in the cache.
+ */
+ private Computation<KeyType, ValueType> _computation;
+
+ /**
+ * Constructs the cached object.
+ *
+ * @param aCache
+ * Cache to use.
+ * @param aObjectKey
+ * Key of the object in the cache.
+ * @param aComputation
+ * Computation to get the object in case the object is not in the
+ * cache.
+ */
+ public CachedObject(Cache<KeyType, ValueType> aCache, KeyType aObjectKey,
+ Computation<KeyType, ValueType> aComputation) {
+ _cache = aCache;
+ _objectKey = aObjectKey;
+ _computation = aComputation;
+ }
+
+ /**
+ * Gets the object. Since the object is cached, different calls to this
+ * method may return different objects.
+ *
+ * @return Object.
+ */
+ public ValueType get() {
+ ValueType object = (ValueType) _cache.get(_objectKey); // the used
+ // cache is
+ // thread safe.
+ if (object == null) {
+ // synchronize the computation to make sure that the object is only
+ // computed
+ // once when multiple concurrent threads detect that the entry must
+ // be
+ // recomputed.
+ synchronized (this) {
+ object = (ValueType) _cache.get(_objectKey);
+ if (object == null) {
+ // No other thread did a recomputation so we must do this
+ // now.
+ LOGGER.debug("Refreshing cache for '" + _objectKey + "'");
+ object = _computation.getObject(_objectKey);
+ _cache.put(_objectKey, object);
+ }
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Invalidates the cache for the object so that it is recomputed the next
+ * time it is requested.
+ *
+ */
+ public void invalidate() {
+ _cache.remove(_objectKey);
+ }
+
+ /**
+ * Gets the cache.
+ *
+ * @return Cache.
+ */
+ public Cache getCache() {
+ return _cache;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+import org.wamblee.io.InputResource;
+
+/**
+ * Cache implemented on top of EhCache.
+ */
+public class EhCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements org.wamblee.cache.Cache<KeyType, ValueType> {
+
+ private static final Logger LOGGER = Logger.getLogger(EhCache.class);
+
+ /**
+ * EH Cache manager.
+ */
+ private CacheManager _manager;
+
+ /**
+ * EH cache.
+ */
+ private Cache _cache;
+
+ /**
+ * Constructs a cache based on EHCache.
+ *
+ * @param aResource
+ * Resource containing the configuration file for EHCache.
+ * @param aCacheName
+ * Name of the cache to use. If a cache with this name does not
+ * exist, one is created based on default settings.
+ * @throws IOException
+ * @throws CacheException
+ */
+ public EhCache(InputResource aResource, String aCacheName)
+ throws IOException, CacheException {
+ InputStream is = aResource.getInputStream();
+ try {
+ _manager = CacheManager.create(is);
+ _cache = _manager.getCache(aCacheName);
+ if (_cache == null) {
+ LOGGER.warn("Creating cache '" + aCacheName
+ + "' because it is not configured");
+ _manager.addCache(aCacheName);
+ _cache = _manager.getCache(aCacheName);
+ }
+ assert _cache != null;
+ } finally {
+ is.close();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ _cache.put(new Element(aKey, aValue));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ try {
+ Element element = _cache.get(aKey);
+ if (element == null) {
+ return null;
+ }
+ return (ValueType) element.getValue();
+ } catch (CacheException e) {
+ throw new RuntimeException("Cache problem key = '" + aKey + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ _cache.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ try {
+ _cache.removeAll();
+ } catch (IOException e) {
+ throw new RuntimeException("Problem removing items from cache", e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * A very simple cache based on a HashMap, It never expires any entries, and has
+ * no bounds on its size.
+ */
+public class ForeverCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ /**
+ * Cached entries.
+ */
+ private HashMap<KeyType, ValueType> _map;
+
+ /**
+ * Constructs the cache.
+ *
+ */
+ public ForeverCache() {
+ _map = new HashMap<KeyType, ValueType>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public synchronized void put(KeyType aKey, ValueType aValue) {
+ _map.put(aKey, aValue);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public synchronized ValueType get(KeyType aKey) {
+ return _map.get(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public synchronized void remove(KeyType aKey) {
+ _map.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public synchronized void clear() {
+ _map.clear();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * A cache that does not cache. This implementation is useful for disabling caching.
+ * Because of this implementation, application code does not need to distinguish
+ * between the situation where it a cache is used and where it isn't.
+ */
+public class ZeroCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ public ZeroCache() {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ // Empty
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ // Empty
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides an interface for a cache together with several
+implementations.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * In memory JVM lock.
+ */
+public class JvmLock implements Lock {
+
+ /**
+ * Reentrant lock to use.
+ */
+ private ReentrantLock _lock;
+
+ /**
+ * In-memory lock.
+ */
+ public JvmLock() {
+ _lock = new ReentrantLock(true);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#acquire()
+ */
+ public void acquire() {
+ _lock.lock();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#release()
+ */
+ public void release() {
+ _lock.unlock();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+/**
+ * Represents a re-entrant lock.
+ * Implementations can provide inmemory JVM locking or full cluster safe locking
+ * mechanisms.
+ */
+public interface Lock {
+
+ /**
+ * Acquires the lock.
+ */
+ void acquire();
+
+ /**
+ * Releases the lock.
+ */
+ void release();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * Locking advice. This automatically synchronized an object using a given lock.
+ */
+public class LockAdvice implements MethodInterceptor {
+
+ /**
+ * Lock to use.
+ */
+ private Lock _lock;
+
+ /**
+ * Constructs lock advice.
+ * @param aLock Lock to use.
+ */
+ public LockAdvice(Lock aLock) {
+ _lock = aLock;
+ }
+
+ /* (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ public Object invoke(MethodInvocation aInvocation) throws Throwable {
+ _lock.acquire();
+ try {
+ return aInvocation.proceed();
+ } finally {
+ _lock.release();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.HashSet;
+
+
+/**
+ * Read-write lock for allowing multiple concurrent readers or at most one
+ * writer. This implementation does not aim for high performance but for
+ * robustness and simplicity. Users of this class should not synchronize on
+ * objects of this class.
+ */
+public class ReadWriteLock {
+ /**
+ * Sets containing the references to the threads that are currently
+ * reading. This administration is useful to check that the lock has
+ * already been acquired before it is release. This check adds robustness
+ * to the application.
+ */
+ private HashSet<Thread> _readers;
+
+ /**
+ * The thread that has acquired the lock for writing or null if no such
+ * thread exists currently.
+ */
+ private Thread _writer;
+
+ /**
+ * Constructs read-write lock.
+ */
+ public ReadWriteLock() {
+ _readers = new HashSet<Thread>();
+ _writer = null;
+ }
+
+ /**
+ * Acquires the lock for reading. This call will block until the lock can
+ * be acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireRead() {
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Read lock already acquired by current thread: "
+ + Thread.currentThread());
+ }
+
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire the read lock while already holding a write lock: "
+ + Thread.currentThread());
+ }
+
+ while (_writer != null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _readers.add(Thread.currentThread());
+ }
+
+ /**
+ * Releases the lock for reading. Note: This implementation assumes that
+ * the lock has already been acquired for reading previously.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired by
+ * this thread.
+ */
+ public synchronized void releaseRead() {
+ if (!_readers.remove(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Cannot release read lock because current thread has not acquired it.");
+ }
+
+ if (_readers.size() == 0) {
+ notifyAll();
+ }
+ }
+
+ /**
+ * Acquires the lock for writing. This call will block until the lock has
+ * been acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireWrite() {
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the write lock: "
+ + Thread.currentThread());
+ }
+
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the read lock: "
+ + Thread.currentThread());
+ }
+
+ // wait until there are no more writers and no more
+ // readers
+ while ((_writer != null) || (_readers.size() > 0)) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _writer = Thread.currentThread();
+
+ // notification not necessary since all writers and
+ // readers are now blocked by this thread.
+ }
+
+ /**
+ * Releases the lock for writing.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired.
+ */
+ public synchronized void releaseWrite() {
+ if (_writer != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Cannot release write lock because it was not acquired. ");
+ }
+
+ _writer = null;
+ notifyAll();
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides utilities for dealing with concurrency.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical and of different boolean conditions.
+ */
+public class AndCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public AndCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the and condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical and.
+ */
+ public AndCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (!condition.matches(aObject)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+
+/**
+ * Determines if an object matches a certain condition.
+ */
+public interface Condition<T> {
+
+ /**
+ * Determines if an object matches a condition.
+ * @param aObject object to match.
+ * @return True iff the object matches.
+ */
+ boolean matches(T aObject);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Condition which always returns a fixed value.
+ */
+public class FixedCondition<T> implements Condition<T> {
+
+ private boolean _value;
+
+ /**
+ * Constructs the condition.
+ * @param aValue Fixed value of the condition.
+ */
+ public FixedCondition(boolean aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical or of different boolean conditions.
+ */
+public class OrCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public OrCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the or condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical or.
+ */
+ public OrCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (condition.matches(aObject)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+/**
+ * Condition to check whether a given property value matches a certain
+ * regular expression.
+ */
+public class PropertyRegexCondition<T> implements Condition<T> {
+
+ /**
+ * Property name.
+ */
+ private String _property;
+
+ /**
+ * Regular expression.
+ */
+ private Pattern _regex;
+
+ /**
+ * Whether or not to convert the value to lowercase before matching.
+ */
+ private boolean _tolower;
+
+ /**
+ * Constructs the condition.
+ * @param aProperty Name of the property to examine.
+ * @param aRegex Regular expression to use.
+ * @param aTolower Whether or not to convert the value to lowercase before matching.
+ */
+ public PropertyRegexCondition(String aProperty, String aRegex, boolean aTolower) {
+ _property = aProperty;
+ _regex = Pattern.compile(aRegex);
+ _tolower = aTolower;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ try {
+ String value = PropertyUtils.getProperty(aObject, _property) + "";
+ if ( _tolower ) {
+ value = value.toLowerCase();
+ }
+ Matcher matcher = _regex.matcher(value);
+ return matcher.matches();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides some basic support classes for checking boolean conditions
+on objects.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Bean factory used to obtain objects in a transparent way.
+ */
+public interface BeanFactory {
+
+ /**
+ * Finds a bean based on id.
+ * @param aId Id of the bean.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ Object find(String aId);
+
+ /**
+ * Finds a bean of the given class and which can be cast to the
+ * specified class. This is typically used by specifying the interface
+ * class for retrieving an implementation of that class. This
+ * means that the bean implementing the class is configured in the bean factory
+ * with id equal to the class name of the interface.
+ * @param aClass Class of the object to find.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(Class<T> aClass);
+
+ /**
+ * Finds a bean with the given id which can be cast to the specified
+ * class.
+ * @param <T> Type of the object to get.
+ * @param aId Id of the object to lookup.
+ * @param aClass Class that the object must extends.
+ * @return Object, always non-null.
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(String aId, Class<T> aClass);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Exception thrown by the BeanFactory if an object could not be found.
+ */
+public class BeanFactoryException extends RuntimeException {
+ static final long serialVersionUID = -1215992188624874902L;
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public BeanFactoryException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aThrowable Cause of the exception.
+ */
+ public BeanFactoryException(String aMsg, Throwable aThrowable) {
+ super(aMsg, aThrowable);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * The standard means to obtain the bean factory. This works by reading a
+ * property {@value #BEAN_FACTORY_CLASS} from a property file named
+ * {@value #BEAN_KERNEL_PROP_FILE} from the class path. This property identifies
+ * the bean factory implementation to use. The configured bean factory must have
+ * a no-arg constructor.
+ */
+public final class BeanKernel {
+
+ private static final Log LOG = LogFactory.getLog(BeanKernel.class);
+
+ /**
+ * Bean factory kernel properties file.
+ */
+ private static final String BEAN_KERNEL_PROP_FILE = "org.wamblee.beanfactory.properties";
+
+ /**
+ * Name of the property to define the name of the bean factory class to use.
+ * THis class must have a public default constructor.
+ */
+ private static final String BEAN_FACTORY_CLASS = "org.wamblee.beanfactory.class";
+
+ /**
+ * Cached bean factory.
+ */
+ private static BeanFactory BEAN_FACTORY;
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private BeanKernel() {
+ // Empty
+ }
+
+ /**
+ * Overrides the default mechanism for looking up the bean factory by
+ * specifying it yourself.
+ *
+ * @param aOverride
+ * Override bean factory.
+ */
+ public static void overrideBeanFactory(BeanFactory aOverride) {
+ BEAN_FACTORY = aOverride;
+ }
+
+ /**
+ * Gets the bean factory.
+ *
+ * @return Bean factory.
+ */
+ public static BeanFactory getBeanFactory() {
+ synchronized (BeanFactory.class) {
+ if (BEAN_FACTORY == null) {
+ BEAN_FACTORY = lookupBeanFactory(BEAN_KERNEL_PROP_FILE);
+ }
+ }
+ return BEAN_FACTORY;
+ }
+
+ /**
+ * Lookup the bean factory based on the properties file.
+ *
+ * @return Bean factory.
+ */
+ static BeanFactory lookupBeanFactory(String aPropertyFilename) {
+ InputResource resource = new ClassPathResource(aPropertyFilename);
+ InputStream is;
+ try {
+ is = resource.getInputStream();
+ } catch (IOException e) {
+ throw new BeanFactoryException("Cannot open resource " + resource,
+ e);
+ }
+ try {
+ Properties props = new Properties();
+ props.load(is);
+ String className = props.getProperty(BEAN_FACTORY_CLASS);
+ Class beanFactory = Class.forName(className);
+ return (BeanFactory) beanFactory.newInstance();
+ } catch (Exception e) {
+ throw new BeanFactoryException("Cannot read from resource "
+ + resource, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // last resort cannot do much now.
+ LOG.error("Error closing resource " + resource);
+ }
+ }
+ }
+}
--- /dev/null
+
+
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Represents a pair of objects. This is inspired on the C++ Standard Template
+ * Library pair template.
+ *
+ * @param <T>
+ * Type of the first object.
+ * @param <U>
+ * Type of the second object.
+ */
+public class Pair<T, U> {
+
+ private T _t;
+
+ private U _u;
+
+ /**
+ * Constructs the pair.
+ *
+ * @param aT
+ * First object.
+ * @param aU
+ * Second object.
+ */
+ public Pair(T aT, U aU) {
+ _t = aT;
+ _u = aU;
+ }
+
+ /**
+ * Copies a pair.
+ *
+ * @param aPair
+ * Pair to copy.
+ */
+ public Pair(Pair<T, U> aPair) {
+ _t = aPair._t;
+ _u = aPair._u;
+ }
+
+ /**
+ * Gets the first object of the pair.
+ *
+ * @return First object.
+ */
+ public T getFirst() {
+ return _t;
+ }
+
+ /**
+ * Gets the second object of the pair.
+ *
+ * @return Second object.
+ */
+ public U getSecond() {
+ return _u;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.access.BeanFactoryLocator;
+import org.springframework.beans.factory.access.BeanFactoryReference;
+import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
+
+/**
+ * Bean factory which uses Spring. This bean factory cannot be configured
+ * directly in the {@link org.wamblee.general.BeanKernel} because it does not
+ * provide a default no-arg constructor. Therefore, it must be delegated to or
+ * it must tbe subclassed to provide a default constructor.
+ */
+public class SpringBeanFactory implements BeanFactory {
+
+ private BeanFactoryReference _factoryReference;
+
+ /**
+ * Constructs the bean factory.
+ *
+ * @param aSelector
+ * Selector to find the appropriate bean ref context.
+ * @param aFactoryName
+ * Spring bean factory to use.
+ */
+ public SpringBeanFactory(String aSelector, String aFactoryName) {
+ try {
+ BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator
+ .getInstance(aSelector);
+ _factoryReference = locator.useBeanFactory(aFactoryName);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(
+ "Could not load bean factory: selector = '" + aSelector
+ + "', factory = '" + aFactoryName + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String,
+ * java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _factoryReference.getFactory().getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several general purpose support classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents an input resource in the classpath.
+ */
+public class ClassPathResource implements InputResource {
+ /**
+ * Resource name.
+ */
+ private String _resource;
+
+ /**
+ * Construct the class path resource.
+ *
+ * @param aResource
+ * Resource
+ */
+ public ClassPathResource(String aResource) {
+ _resource = aResource;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream stream = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(_resource);
+ if (stream == null) {
+ throw new IOException("Class path resource '" + _resource
+ + "' not found.");
+ }
+ return stream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "ClassPathResource(" + _resource + ")";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Resource implemention for reading from a file.
+ */
+public class FileResource implements InputResource {
+ /**
+ * File to read.
+ */
+ private File _file;
+
+ /**
+ * Constructs the resource.
+ *
+ * @param aFile
+ * File to read.
+ */
+ public FileResource(File aFile) {
+ _file = aFile;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(_file));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a resource from which information can be read.
+ */
+public interface InputResource {
+ /**
+ * Gets the input stream to the resource. The obtained input stream must be
+ * closed once reading has finished.
+ *
+ * @return Input stream to the resource, never null.
+ * @throws IOException
+ * in case the resource cannot be found.
+ */
+ InputStream getInputStream() throws IOException;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input resource based on an input stream.
+ */
+public class StreamResource implements InputResource {
+ /**
+ * Input stream to read.
+ */
+ private InputStream _stream;
+
+ /**
+ * Constructs a resource.
+ *
+ * @param aStream
+ * Input stream to read.
+ */
+ public StreamResource(InputStream aStream) {
+ _stream = aStream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return _stream;
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several support utilities for IO related functionality.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Default observer notifier which calls
+ * {@link org.wamblee.observer.Observer#send(ObservableType, Event)}
+ * immediately.
+ */
+public class DefaultObserverNotifier<ObservableType, Event> implements
+ ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Constructs the notifier.
+ *
+ */
+ public DefaultObserverNotifier() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.observer.ObserverNotifier#update(org.wamblee.observer.Observer,
+ * ObservableType, Event)
+ */
+ public void update(Observer<ObservableType, Event> aObserver,
+ ObservableType aObservable, Event aEvent) {
+ aObserver.send(aObservable, aEvent);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements subscription and notification logic for an observer pattern. This
+ * class is thread safe.
+ */
+public class Observable<ObservableType, Event> {
+
+ private static final Logger LOGGER = Logger.getLogger(Observable.class);
+
+ /**
+ * Observable.
+ */
+ private ObservableType _observable;
+
+ /**
+ * Used to notify observers.
+ */
+ private ObserverNotifier<ObservableType, Event> _notifier;
+
+ /**
+ * Map of subscription to observer.
+ */
+ private Map<Long, Observer<ObservableType, Event>> _observers;
+
+ /**
+ * Counter for subscriptions. Holds the next subscription.
+ */
+ private long _counter;
+
+ /**
+ * Constructs the observable.
+ *
+ * @param aObservable
+ * Observable this instance is used for.
+ * @param aNotifier
+ * Object used for implementing notification of listeners.
+ */
+ public Observable(ObservableType aObservable,
+ ObserverNotifier<ObservableType, Event> aNotifier) {
+ _observable = aObservable;
+ _notifier = aNotifier;
+ _observers = new TreeMap<Long, Observer<ObservableType, Event>>();
+ _counter = 0;
+ }
+
+ /**
+ * Subscribe an obvers.
+ *
+ * @param aObserver
+ * Observer to subscribe.
+ * @return Event Event to send.
+ */
+ public synchronized long subscribe(Observer<ObservableType, Event> aObserver) {
+ long subscription = _counter++; // integer rage is so large it will
+ // never roll over.
+ _observers.put(subscription, aObserver);
+ return subscription;
+ }
+
+ /**
+ * Unsubscribe an observer.
+ *
+ * @param aSubscription
+ * Subscription which is used
+ * @throws IllegalArgumentException
+ * In case the subscription is not known.
+ */
+ public synchronized void unsubscribe(long aSubscription) {
+ Object obj = _observers.remove(aSubscription);
+ if (obj == null) {
+ throw new IllegalArgumentException("Subscription '" + aSubscription
+ + "'");
+ }
+ }
+
+ /**
+ * Gets the number of subscribed observers.
+ *
+ * @return Number of subscribed observers.
+ */
+ public int getObserverCount() {
+ return _observers.size();
+ }
+
+ /**
+ * Notifies all subscribed observers.
+ *
+ * @param aEvent
+ * Event to send.
+ */
+ public void send(Event aEvent) {
+ // Make sure we do the notification while not holding the lock to avoid
+ // potential deadlock
+ // situations.
+ List<Observer<ObservableType, Event>> observers = new ArrayList<Observer<ObservableType, Event>>();
+ synchronized (this) {
+ observers.addAll(_observers.values());
+ }
+ for (Observer<ObservableType, Event> observer : observers) {
+ _notifier.update(observer, _observable, aEvent);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ if (_observers.size() > 0) {
+ LOGGER
+ .error("Still observers registered at finalization of observer!");
+ for (Observer observer : _observers.values()) {
+ LOGGER.error(" observer: " + observer);
+ }
+ }
+
+ super.finalize();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * This is a type-safe version of {@link java.util.Observable}.
+ */
+public interface Observer<ObservableType, Event> {
+
+ /**
+ * Called when an event has occurred on the observable.
+ * @param aObservable Observable.
+ * @param aEvent Event.
+ */
+ void send(ObservableType aObservable, Event aEvent);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Implementation of notification of subscribers.
+ */
+public interface ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Notifies an observer.
+ *
+ * @param aObserver Observer to notify
+ * @param aObservable Observable at which the event occured.
+ * @param aEvent Event that occured.
+ */
+ void update(Observer<ObservableType, Event> aObserver, ObservableType aObservable, Event aEvent);
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for the observer pattern.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of Persistent.
+ */
+public abstract class AbstractPersistent implements Persistent {
+
+ /**
+ * Primary key.
+ */
+ private Serializable _primaryKey;
+
+ /**
+ * Version.
+ */
+ private int _version;
+
+ /**
+ * Constructs the object.
+ *
+ */
+ protected AbstractPersistent() {
+ _primaryKey = null;
+ _version = -1;
+ }
+
+ /**
+ * Copy constructor.
+ * @param aPersistent Object to copy.
+ */
+ protected AbstractPersistent(AbstractPersistent aPersistent) {
+ _primaryKey = aPersistent._primaryKey;
+ _version = aPersistent._version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPrimaryKey()
+ */
+ public Serializable getPrimaryKey() {
+ return _primaryKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPrimaryKey(java.io.Serializable)
+ */
+ public void setPrimaryKey(Serializable aKey) {
+ _primaryKey = aKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPersistedVersion()
+ */
+ public int getPersistedVersion() {
+ return _version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPersistedVersion(int)
+ */
+ public void setPersistedVersion(int aVersion) {
+ _version = aVersion;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Interface for persistent objects. This defines required functionality for all objects
+ * that are persisted.
+ *
+ * Objects that implement this interface and which implement
+ * {@link java.lang.Object#equals(java.lang.Object)}
+ * should exclude the primary key and version from determining equality.
+ */
+public interface Persistent {
+
+ /**
+ * Gets the primary key.
+ * @return Primary key.
+ * @see #setPrimaryKey(Serializable)
+ */
+ Serializable getPrimaryKey();
+
+ /**
+ * Sets the primary key.
+ * @param aKey Primary key.
+ * @see #getPrimaryKey()
+ */
+ void setPrimaryKey(Serializable aKey);
+
+ /**
+ * Gets the version.
+ * @return Version.
+ * @see #setPersistedVersion(int)
+ */
+ int getPersistedVersion();
+
+ /**
+ * Sets the version.
+ * @param aVersion Version.
+ * @see #getPersistedVersion()
+ */
+ void setPersistedVersion(int aVersion);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Hibernate mapping files to use.
+ */
+public class HibernateMappingFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of hibernate mapping files.
+ *
+ */
+ public HibernateMappingFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public HibernateMappingFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Extension of
+ * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
+ */
+public class HibernateSupport extends HibernateDaoSupport {
+
+ private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
+
+ /**
+ * This class provided an equality operation based on the object reference
+ * of the wrapped object. This is required because we cannto assume that the
+ * equals operation has any meaning for different types of persistent
+ * objects. This allows us to use the standard collection classes for
+ * detecting cyclic dependences and avoiding recursion.
+ *
+ */
+ private static final class ObjectElem {
+ private Object _object;
+
+ public ObjectElem(Object aObject) {
+ _object = aObject;
+ }
+
+ public boolean equals(Object aObj) {
+ return ((ObjectElem) aObj)._object == _object;
+ }
+
+ public int hashCode() {
+ return _object.hashCode();
+ }
+ }
+
+ /**
+ * Constructs the object.
+ *
+ */
+ public HibernateSupport() {
+ // Empty
+ }
+
+ /**
+ * Performes a hibernate <code>Session.merge()</code> and updates the
+ * object with the correct primary key and version. This is an extension to
+ * the hibernate merge operation because hibernate itself leaves the object
+ * passed to merge untouched.
+ *
+ * Use this method with extreme caution since it will recursively load all
+ * objects that the current object has relations with and for which
+ * cascade="merge" was specified in the Hibernate mapping file.
+ *
+ * @param aPersistent
+ * Object to merge.
+ */
+ public void merge(Persistent aPersistent) {
+ merge(getHibernateTemplate(), aPersistent);
+ }
+
+ /**
+ * As {@link #merge(Persistent)} but with a given template. This method can
+ * be accessed in a static way.
+ *
+ * @param aTemplate
+ * Hibernate template
+ * @param aPersistent
+ * Object to merge.
+ */
+ public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
+ Persistent merged = (Persistent) aTemplate.merge(aPersistent);
+ processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
+ }
+
+ /**
+ * Copies primary keys and version from the result of the merged to the
+ * object that was passed to the merge operation. It does this by traversing
+ * the properties of the object. It copies the primary key and version for
+ * objects that implement {@link Persistent} and applies the same rules to
+ * objects in maps and sets as well (i.e. recursively).
+ *
+ * @param aPersistent
+ * Object whose primary key and version are to be set.
+ * @param aMerged
+ * Object that was the result of the merge.
+ * @param aProcessed
+ * List of already processed Persistent objects of the persistent
+ * part.
+ */
+ public static void processPersistent(Persistent aPersistent,
+ Persistent aMerged, List<ObjectElem> aProcessed) {
+ if (aPersistent == null && aMerged == null) {
+ return;
+ }
+ if (aPersistent == null || aMerged == null) {
+ throw new RuntimeException("persistent or merged object is null '"
+ + aPersistent + "'" + " '" + aMerged + "'");
+ }
+ ObjectElem elem = new ObjectElem(aPersistent);
+ if (aProcessed.contains(elem)) {
+ return; // already processed.
+ }
+ aProcessed.add(elem);
+
+ LOG.info("Setting pk/version on " + aPersistent + " from " + aMerged);
+
+ if (aPersistent.getPrimaryKey() != null
+ && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
+ LOG.error("Mismatch between primary key values: " + aPersistent
+ + " " + aMerged);
+ } else {
+ aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
+ aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
+ }
+
+ Method[] methods = aPersistent.getClass().getMethods();
+ for (Method getter : methods) {
+ if (getter.getName().startsWith("get")) {
+ Class returnType = getter.getReturnType();
+
+ try {
+ if (Set.class.isAssignableFrom(returnType)) {
+ Set merged = (Set) getter.invoke(aMerged);
+ Set persistent = (Set) getter.invoke(aPersistent);
+ processSet(persistent, merged, aProcessed);
+ } else if (List.class.isAssignableFrom(returnType)) {
+ List merged = (List) getter.invoke(aMerged);
+ List persistent = (List) getter.invoke(aPersistent);
+ processList(persistent, merged, aProcessed);
+ } else if (Map.class.isAssignableFrom(returnType)) {
+ Map merged = (Map) getter.invoke(aMerged);
+ Map persistent = (Map) getter.invoke(aPersistent);
+ processMap(persistent, merged, aProcessed);
+ } else if (Persistent.class.isAssignableFrom(returnType)) {
+ Persistent merged = (Persistent) getter.invoke(aMerged);
+ Persistent persistent = (Persistent) getter
+ .invoke(aPersistent);
+ processPersistent(persistent, merged, aProcessed);
+ } else if (returnType.isArray()
+ && Persistent.class.isAssignableFrom(returnType
+ .getComponentType())) {
+ Persistent[] merged = (Persistent[]) getter
+ .invoke(aMerged);
+ Persistent[] persistent = (Persistent[]) getter
+ .invoke(aPersistent);
+ for (int i = 0; i < persistent.length; i++) {
+ processPersistent(persistent[i], merged[i],
+ aProcessed);
+ }
+ }
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Process the persistent objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processList(List aPersistent, List aMerged,
+ List<ObjectElem> aProcessed) {
+ Object[] merged = aMerged.toArray();
+ Object[] persistent = aPersistent.toArray();
+ if (merged.length != persistent.length) {
+ throw new RuntimeException("Array sizes differ " + merged.length
+ + " " + persistent.length);
+ }
+ for (int i = 0; i < merged.length; i++) {
+ assert merged[i].equals(persistent[i]);
+ if (merged[i] instanceof Persistent) {
+ processPersistent((Persistent) persistent[i],
+ (Persistent) merged[i], aProcessed);
+ }
+ }
+ }
+
+ /**
+ * Process the persistent objects in sets.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processSet(Set aPersistent, Set aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Array sizes differ " + aMerged.size()
+ + " " + aPersistent.size());
+ }
+ for (Object merged : aMerged) {
+ // Find the object that equals the merged[i]
+ for (Object persistent : aPersistent) {
+ if (persistent.equals(merged)) {
+ processPersistent((Persistent) persistent,
+ (Persistent) merged, aProcessed);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the Map objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processMap(Map aPersistent, Map aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Sizes differ " + aMerged.size() + " "
+ + aPersistent.size());
+ }
+ Set keys = aMerged.keySet();
+ for (Object key : keys) {
+ if (!aPersistent.containsKey(key)) {
+ throw new RuntimeException("Key '" + key + "' not found");
+ }
+ Object mergedValue = aMerged.get(key);
+ Object persistentValue = aPersistent.get(key);
+ if (mergedValue instanceof Persistent) {
+ if (persistentValue instanceof Persistent) {
+ processPersistent((Persistent) persistentValue,
+ (Persistent) mergedValue, aProcessed);
+ } else {
+ throw new RuntimeException(
+ "Value in original object is null, whereas merged object contains a value");
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for persistence with hibernate.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for persistence.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * URI resolver that resolves stylesheets through the classpath.
+ */
+public class ClasspathUriResolver implements URIResolver {
+
+ /**
+ * Constructs the resolver.
+ *
+ */
+ public ClasspathUriResolver() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.xml.transform.URIResolver#resolve(java.lang.String,
+ * java.lang.String)
+ */
+ public Source resolve(String aHref, String aBase)
+ throws TransformerException {
+ InputResource xslt = new ClassPathResource(aHref);
+ try {
+ return new StreamSource(xslt.getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(
+ "Could not get XSLT style sheet in classpath '" + aHref
+ + "'", e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.dom4j.DocumentException;
+import org.dom4j.io.DOMReader;
+import org.dom4j.io.DOMWriter;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;
+import com.sun.org.apache.xerces.internal.jaxp.validation.xs.SchemaFactoryImpl;
+
+/**
+ * Some basic XML utilities for common reoccuring tasks for DOM documents.
+ */
+public final class DomUtils {
+
+ private static final Log LOG = LogFactory.getLog(DomUtils.class);
+
+ /**
+ * Disabled default constructor.
+ *
+ */
+ private DomUtils() {
+ // Empty.
+ }
+
+ /**
+ * Parses an XML document from a string.
+ *
+ * @param aDocument
+ * document.
+ * @return
+ */
+ public static Document read(String aDocument) throws XMLException {
+ ByteArrayInputStream is = new ByteArrayInputStream(aDocument.getBytes());
+ return read(is);
+ }
+
+ /**
+ * Parses an XML document from a stream.
+ *
+ * @param aIs
+ * Input stream.
+ * @return
+ */
+ public static Document read(InputStream aIs) throws XMLException {
+ try {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ return builder.parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+ }
+
+ /**
+ * Reads and validates a document against a schema.
+ *
+ * @param aIs
+ * Input stream.
+ * @param aSchema
+ * Schema.
+ * @return Parsed and validated document.
+ */
+ public static Document readAndValidate(InputStream aIs, InputStream aSchema)
+ throws XMLException {
+
+ try {
+ final Schema schema = SchemaFactory.newInstance(
+ XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
+ new StreamSource(aSchema));
+
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(true);
+ factory.setNamespaceAware(true);
+ factory.setSchema(schema);
+
+ return factory.newDocumentBuilder().parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aSchema.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing schema", e);
+ }
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+
+ }
+
+ /**
+ * Serializes an XML document to a stream.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @param aOs
+ * Output stream.
+ */
+ public static void serialize(Document aDocument, OutputStream aOs)
+ throws IOException {
+ XMLSerializer serializer = new XMLSerializer(aOs, new OutputFormat());
+ serializer.serialize(aDocument);
+ }
+
+ /**
+ * Serializes an XML document.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @return Serialized document.
+ */
+ public static String serialize(Document aDocument) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ serialize(aDocument, os);
+ return os.toString();
+ }
+
+ /**
+ * Converts a dom4j document into a w3c DOM document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return W3C DOM document.
+ */
+ public static Document convert(org.dom4j.Document aDocument)
+ throws DocumentException {
+ return new DOMWriter().write(aDocument);
+ }
+
+ /**
+ * Converts a W3C DOM document into a dom4j document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return Dom4j document.
+ */
+ public static org.dom4j.Document convert(Document aDocument) {
+ return new DOMReader().read(aDocument);
+ }
+
+ /**
+ * Removes duplicate attributes from a DOM tree.This is useful for
+ * postprocessing the output of JTidy as a workaround for a bug in JTidy.
+ *
+ * @param aNode
+ * Node to remove duplicate attributes from (recursively).
+ * Attributes of the node itself are not dealt with. Only the
+ * child nodes are dealt with.
+ */
+ public static void removeDuplicateAttributes(Node aNode) {
+ NodeList list = aNode.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element) {
+ removeDuplicateAttributes((Element) node);
+ removeDuplicateAttributes(node);
+ }
+ }
+ }
+
+ /**
+ * Removes duplicate attributes from an element.
+ *
+ * @param aElement
+ * Element.
+ */
+ private static void removeDuplicateAttributes(Element aElement) {
+ NamedNodeMap attributes = aElement.getAttributes();
+ Map<String, Attr> uniqueAttributes = new TreeMap<String, Attr>();
+ List<Attr> attlist = new ArrayList<Attr>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ if (uniqueAttributes.containsKey(attribute.getNodeName())) {
+ LOG.info("Detected duplicate attribute (will be removed)'"
+ + attribute.getNodeName() + "'");
+ }
+ uniqueAttributes.put(attribute.getNodeName(), attribute);
+ attlist.add(attribute);
+ }
+ // Remove all attributes from the element.
+ for (Attr att : attlist) {
+ aElement.removeAttributeNode(att);
+ }
+ // Add the unique attributes back to the element.
+ for (Attr att : uniqueAttributes.values()) {
+ aElement.setAttributeNode(att);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+/**
+ * Exception thrown in case of XML parsing problems.
+ */
+public class XMLException extends Exception {
+
+ public XMLException(String aMsg) {
+ super(aMsg);
+ }
+
+ public XMLException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Document;
+import org.wamblee.io.FileResource;
+
+/**
+ * XSL transformer for simplified usage of XSL transformations.
+ */
+public class XslTransformer {
+
+ private TransformerFactory _factory;
+
+ /**
+ * Constructs the URL resolver.
+ *
+ * @param aResolver
+ * URI resolver to use.
+ */
+ public XslTransformer(URIResolver aResolver) {
+ _factory = TransformerFactory.newInstance();
+ _factory.setURIResolver(aResolver);
+ }
+
+ /**
+ * Constructs the XSLT processor.
+ *
+ */
+ public XslTransformer() {
+ _factory = TransformerFactory.newInstance();
+ }
+
+ /**
+ * Resolves an XSLT based on URI.
+ * @param aXslt XSLT to resolve,
+ * @return Source for the XSLT
+ * @throws TransformerException In case the XSLT cannot be found.
+ */
+ public Source resolve(String aXslt) throws TransformerException {
+ URIResolver resolver = _factory.getURIResolver();
+ if (resolver == null) {
+ if (new File(aXslt).canRead()) {
+ try {
+ return new StreamSource(new FileResource(new File(aXslt))
+ .getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(e.getMessage(), e);
+ }
+ } else {
+ throw new TransformerException("Cannot read '" + aXslt + "'");
+ }
+ }
+ return resolver.resolve(aXslt, "");
+ }
+
+ /**
+ * Transforms a DOM document into another DOM document using a given XSLT
+ * transformation.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(Document aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new DOMSource(aDocument);
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document to a text output. This supports XSLT
+ * transformations that result in text documents.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSL transformation.
+ * @return Transformed document.
+ */
+ public String textTransform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamResult result = new StreamResult(os);
+ transform(source, result, aXslt);
+ return new String(os.toByteArray());
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aSource
+ * Document to transform.
+ * @param aResult
+ * Result of the transformation.
+ * @param aXslt
+ * XSLT to use.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public void transform(Source aSource, Result aResult, Source aXslt)
+ throws IOException, TransformerException {
+ try {
+ Transformer transformer = _factory.newTransformer(aXslt);
+ transformer.transform(aSource, aResult);
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException(
+ "Configuration problem of XSLT transformation", e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for XML processing.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import net.sf.ehcache.CacheException;
+
+import org.wamblee.io.TestResource;
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Cached object test.
+ */
+public class CachedObjectTest extends TestCase {
+
+ /**
+ *
+ */
+ private static final String EHCACHE_CONFIG = "ehcache.xml";
+
+ private static final int OBJECT_KEY = 10;
+
+ private CachedObject.Computation<Integer,Integer> _computation;
+ private int _ncomputations;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _computation = new CachedObject.Computation<Integer,Integer>() {
+ public Integer getObject(Integer aObjectKey) {
+ _ncomputations++;
+ return compute(aObjectKey);
+ };
+ };
+ _ncomputations = 0;
+ }
+
+ private int compute(int aValue) {
+ return aValue + 10;
+ }
+
+ private CachedObject<Integer, Integer> createCached(Cache<Integer,Integer> aCache) {
+ return new CachedObject<Integer, Integer>(aCache, OBJECT_KEY, _computation);
+ }
+
+ /**
+ * Verifies that upon first use, the cached object uses the computation to
+ * retrieve the object.
+ *
+ */
+ public void testComputation() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+
+ public void testInvalidateCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+ }
+
+ public void testBehaviorEhCache() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "test");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // explicit invalidation.
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(3, _ncomputations);
+
+ }
+
+ public void testBehaviorEhCacheDefault() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "undefined");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ }
+
+
+ public void testBehaviorForeverCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+ }
+
+ public void testBehaviorZeroCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(ncomp, _ncomputations);
+ }
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(101, _ncomputations);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.EventTracker;
+import org.wamblee.test.TimingUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the JVMLock.
+ */
+public abstract class AbstractLockTestCase extends TestCase {
+
+ protected static final int SLEEP_TIME = 1000;
+
+ protected static final String STARTED = "started";
+
+ protected static final String ACQUIRED = "acquired";
+
+ protected static final String RELEASED = "released";
+
+ private EventTracker<String> _tracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _tracker = new EventTracker<String>();
+ }
+
+ protected EventTracker<String> getTracker() {
+ return _tracker;
+ }
+
+ /**
+ * Must be implemented to generate the events
+ * {@link #STARTED}, {@link #ACQUIRED}, and {@link #RELEASED} in
+ * that order. The lock should be acquired for
+ * the time specified by {@link #SLEEP_TIME}.
+ * @return Thread which does the work.
+ */
+ protected abstract Thread runThread();
+
+ /**
+ * Tests the operation of the lock.
+ */
+ public void testLock() throws InterruptedException {
+ Thread t1 = runThread();
+ Thread t2 = runThread();
+ TimingUtils.sleep(SLEEP_TIME / 10); // give threads a chance to start
+ // up.
+ assertEquals(2, _tracker.getEventCount(STARTED)); // both threads
+ // should have
+ // started.
+ assertEquals(1, _tracker.getEventCount(ACQUIRED)); // one thread has
+ // acquired the
+ // lock.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(ACQUIRED)); // now the other
+ // thread could also
+ // acquire the lock
+ assertEquals(1, _tracker.getEventCount(RELEASED)); // and the first
+ // thread has
+ // released it.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(RELEASED)); // both threads
+ // should be
+ // finished.
+ t1.join();
+ t2.join();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Tests for the JVMLock.
+ */
+public class JvmLockTest extends AbstractLockTestCase {
+
+ private JvmLock _lock;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new JvmLock();
+ }
+
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ getTracker().eventOccurred(STARTED);
+ _lock.acquire();
+ getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ _lock.release();
+ getTracker().eventOccurred(RELEASED);
+ };
+ });
+ t.start();
+ return t;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.springframework.aop.framework.ProxyFactoryBean;
+import org.wamblee.test.TimingUtils;
+
+/**
+ *
+ */
+public class LockAdviceTest extends AbstractLockTestCase {
+
+ private class Runner implements Runnable {
+ public void run() {
+ LockAdviceTest.this.getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ }
+ }
+
+ private Runnable _target;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Runner runner = new Runner();
+ LockAdvice advice = new LockAdvice(new JvmLock());
+
+ ProxyFactoryBean support = new ProxyFactoryBean();
+ support.setInterfaces(new Class[]{ Runnable.class });
+ support.setTarget(runner);
+ support.addAdvice(advice);
+ _target = (Runnable)support.getObject();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#runThread()
+ */
+ @Override
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ getTracker().eventOccurred(STARTED);
+ _target.run();
+ getTracker().eventOccurred(RELEASED);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ };
+ });
+ t.start();
+ return t;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+
+/**
+ * Testing the read-write lock class. Note: in case of problems, test cases
+ * could hang.
+ *
+ * @see ReadWriteLock
+ */
+public class ReadWriteLockTest extends TestCase {
+ /**
+ *
+ */
+ private static final int HALF_SECOND = 500;
+ /**
+ *
+ */
+ private static final int ONE_SECOND = 1000;
+ /**
+ *
+ */
+ private static final int TWO_SECONDS = 2000;
+ private ReadWriteLock _lock;
+ private int _nReaders;
+ private int _nWriters;
+
+ /**
+ * Constructor for ReadWriteLockTest.
+ *
+ * @param aName
+ */
+ public ReadWriteLockTest(String aName) {
+ super(aName);
+ }
+
+ private synchronized int getReaderCount() {
+ return _nReaders;
+ }
+
+ private synchronized int getWriterCount() {
+ return _nWriters;
+ }
+
+ synchronized void incrementReaderCount() {
+ _nReaders++;
+ }
+
+ synchronized void incrementWriterCount() {
+ _nWriters++;
+ }
+
+ synchronized void decrementReaderCount() {
+ _nReaders--;
+ }
+
+ synchronized void decrementWriterCount() {
+ _nWriters--;
+ }
+
+ /*
+ * @see TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new ReadWriteLock();
+ }
+
+ /*
+ * @see TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ _lock = null;
+ super.tearDown();
+ }
+
+ /**
+ * Acquire and release a read lock.
+ */
+ public void testRead() {
+ _lock.acquireRead();
+ _lock.releaseRead();
+ }
+
+ /**
+ * Acquire and release a write lock.
+ */
+ public void testWrite() {
+ _lock.acquireWrite();
+ _lock.releaseWrite();
+ }
+
+ /**
+ * Verify concurrent access by multiple readers is possible.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleReaders() throws InterruptedException {
+ Runnable runnable = new ReadLocker(_lock, this, TWO_SECONDS);
+
+ Thread t1 = new Thread(runnable);
+ t1.start();
+
+ Thread t2 = new Thread(runnable);
+ t2.start();
+ Thread.sleep(ONE_SECOND);
+ assertTrue("Not enough readers!", getReaderCount() == 2);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that only one writer at a time can acquire the write lock.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testSingleWriter() throws InterruptedException {
+ WriteLocker writer = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer);
+ Thread t2 = new Thread(writer);
+
+ t1.start();
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue("Wrong writer count: " + getWriterCount(),
+ getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that multiple writers cannot acquire the write lock concurrently.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleWriters() throws InterruptedException {
+ WriteLocker writer1 = new WriteLocker(_lock, this, HALF_SECOND + ONE_SECOND);
+ WriteLocker writer2 = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer1);
+ Thread t2 = new Thread(writer2);
+
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1); // first writer still
+
+ // has the lock.
+ Thread.sleep(ONE_SECOND);
+
+ // at t = 2, the second writer still must have
+ // a lock.
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that after the first reader acquires a lock, a subsequent writer
+ * can only acquire the lock after the reader has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite1() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, reader still holding the
+ // lock so write lock cannot be acquired.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, read lock released and
+ // write lock must be acquired.
+ assertTrue("Wrong no. of readers: " + getReaderCount(),
+ getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that when multiple readers have acquired a read lock, the writer
+ * can only acquire the lock after all readers have released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite2() throws InterruptedException {
+ ReadLocker readLocker1 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ ReadLocker readLocker2 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ Thread t1 = new Thread(readLocker1);
+ Thread t2 = new Thread(readLocker2);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t3 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(ONE_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 2);
+ t3.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 2 seconds,
+ assertTrue(getReaderCount() == 2);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND);
+
+ // 3 seconds underway, first read lock must
+ // have been released.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(HALF_SECOND);
+
+ // 4 seconds underway, write lock must have
+ // been acquired.
+ assertTrue(getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+
+ t1.join();
+ t2.join();
+ t3.join();
+ }
+
+ /**
+ * Verify that after a writer acquires a lock, a subsequent reader can
+ * only acquire the lock after the writer has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite3() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t2.start(); // acquire write lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, writer still holding the
+ // lock so read lock cannot be acquired.
+ assertTrue(getWriterCount() == 1);
+ assertTrue(getReaderCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, write lock released and
+ // read lock must be acquired.
+ assertTrue("Wrong no. of writers: " + getReaderCount(),
+ getWriterCount() == 0);
+ assertTrue(getReaderCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /*
+ * The following test cases are for testing whether or not
+ * the read write lock checks the locking correctly.
+ * Strictly speaking, these checks wouldn't be necessary
+ * because it involves the contract of the ReadWriteLock which
+ * must be obeyed by users of the ReadWriteLock. Nevertheless,
+ * this is tested anyway to be absolutely sure.
+ */
+
+ /**
+ * Acquire a read lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseReadFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireRead();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseRead(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseWriteFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireWrite();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseWrite(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a read lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadTwice() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ // ok
+ return;
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a write lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteTwice() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for reading and directly afterwards acquire it for
+ * writing. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for writing and directly afterwards acquire it for
+ * reading. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a read lock and release it as a write lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByReleaseaWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.releaseWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock and release it as a read lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByReleaseRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.releaseRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+}
+
+
+/**
+ * ReadLocker acquires a read lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the read
+ * lock, and performs a callback after the lock has been released.
+ */
+class ReadLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireRead();
+ _lockTest.incrementReaderCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("ReadLocker thread was interrupted."
+ + Thread.currentThread());
+ }
+
+ _lock.releaseRead();
+ _lockTest.decrementReaderCount();
+ }
+}
+
+
+/**
+ * WriteLocker acquires a write lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the write
+ * lock, and performs a callback after the lock has been released.
+ */
+class WriteLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireWrite();
+ _lockTest.incrementWriterCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("WriteLocker thread was interrupted: "
+ + Thread.currentThread());
+ }
+
+ _lock.releaseWrite();
+ _lockTest.decrementWriterCount();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ */
+public class AndConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ AndCondition<Integer> and = new AndCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, and.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ AndCondition<Integer> and = new AndCondition<Integer>(conditions);
+ assertEquals(aResult, and.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, false);
+ checkResult(false, true, false);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, false);
+ checkResult(new boolean[]{ false, true, false }, false);
+ checkResult(new boolean[]{ false, false, true }, false);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ */
+public class GreaterThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public GreaterThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject > _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ */
+public class LessThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public LessThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject < _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ */
+public class OrConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ OrCondition<Integer> or = new OrCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, or.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ OrCondition<Integer> or = new OrCondition<Integer>(conditions);
+ assertEquals(aResult, or.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, true);
+ checkResult(false, true, true);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, true);
+ checkResult(new boolean[]{ false, true, false }, true);
+ checkResult(new boolean[]{ false, false, true }, true);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link org.wamblee.conditions.PropertyRegexCondition}.
+ */
+public class PropertyRegexConditionTest extends TestCase {
+
+ private boolean match(String aProperty, String aRegex, boolean aToLower, TestBean aBean) {
+ PropertyRegexCondition<TestBean> condition = new PropertyRegexCondition<TestBean>(aProperty, aRegex, aToLower );
+ return condition.matches(aBean);
+ }
+
+ private void checkMatch(String aProperty, String aRegex, boolean aToLower, TestBean aBean, boolean aResult) {
+ assertEquals( aResult, match(aProperty, aRegex, aToLower, aBean));
+ }
+
+ /**
+ * Verifies correct matching behavior for several cases.
+ *
+ */
+ public void testMatchProperty() {
+ TestBean bean = new TestBean("Hallo");
+ checkMatch("value", "Hallo", false, bean, true);
+ checkMatch("value", "all", false, bean, false);
+ checkMatch("value", ".a.*o", false, bean, true);
+ checkMatch("value", "hallo", false, bean, false); // no match when not converting to lower case.
+ checkMatch("value", "hallo", true, bean, true); // match!
+ }
+
+ /**
+ * Uses property regex condition for non-existing property.
+ * Verifies that a runtime exception is thrown.
+ *
+ */
+ public void testWrongProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("bla", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Applies condition to a private property. Verifies that a runtime
+ * exception is thrown.
+ *
+ */
+ public void testPrivateProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("privateValue", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ *
+ */
+public class TestBean {
+
+ private String _value;
+
+ public TestBean(String aValue) {
+ _value = aValue;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ private String getPrivateValue() {
+ return _value;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the bean kernel. The lookup of the bean factory itself can be tested
+ * only partially. Using a global property file for all test cases would tie all
+ * test cases together therefore no global property file is used.
+ */
+public class BeanKernelTest extends TestCase {
+
+ /**
+ * Loads the bean factory based on a property file configuration. Verifies
+ * the correct bean factory is loaded.
+ *
+ */
+ public void testLoadBeanFactoryFromProperties() {
+ BeanFactory factory = BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel.properties");
+ assertTrue(factory instanceof TestBeanFactory);
+ }
+
+ /**
+ * Loads the bean factory based on a non-existing property file.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentPropertyFile() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-nonexistent.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Loads the bean factory based on a property file with a non-existing
+ * bean factory defined in it.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentBeanFactory() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-wrong.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Retrieves a bean factory throug the bean kernel. Verifies that beans can
+ * be retrieved.
+ *
+ */
+ public void testRetrieveFactory() {
+ BeanKernel.overrideBeanFactory(new TestBeanFactory()); // bypass
+ // default
+ // property
+ // lookup
+ BeanFactory factory = BeanKernel.getBeanFactory();
+ assertNotNull(factory);
+ assertEquals("hello", factory.find(String.class));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the pair class.
+ */
+public class PairTest extends TestCase {
+
+ public void testPair() {
+ Pair<Integer, String> pair = new Pair<Integer, String>(10, "hello");
+ assertEquals(new Integer(10), pair.getFirst());
+ assertEquals("hello", pair.getSecond());
+
+ Pair<Integer, String> pair2 = new Pair<Integer, String>(pair);
+ assertEquals(new Integer(10), pair2.getFirst());
+ assertEquals("hello", pair2.getSecond());
+
+
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the spring bean factory.
+ */
+public class SpringBeanFactoryTest extends TestCase {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ public void testExistingBeanRefContext() {
+ SpringBeanFactory factory = new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "test");
+
+ String value1 = factory.find(String.class);
+ assertEquals("hello", value1);
+ String value2 = (String) factory.find("java.lang.String");
+ assertEquals("hello", value2);
+ String value3 = factory.find("java.lang.String", String.class);
+ assertEquals("hello", value3);
+
+ try {
+ factory.find("unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ public void testUnknownBeanFactory() {
+ try {
+ new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Test bean factory.
+ */
+public class TestBeanFactory extends SpringBeanFactory {
+
+
+ public TestBeanFactory() {
+ super("org/wamblee/general/beanRefContext.xml", "test");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the classpath resource.
+ */
+public class ClassPathResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource from the class path. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource.txt");
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource from the class path. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource-nonexistent.txt");
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the file resource.
+ */
+public class FileResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource.txt"));
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource-nonexistent.txt"));
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.security.CodeSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * File system utilities.
+ */
+public final class FileSystemUtils {
+
+ private static final Log LOG = LogFactory.getLog(FileSystemUtils.class);
+
+ /**
+ * Test output directory relative to the sub project.
+ */
+ private static final String TEST_OUTPUT_DIR = "resources/testoutput";
+
+ /**
+ * Test input directory relative to the sub project.
+ */
+ private static final String TEST_INPUT_DIR = "../resources/test";
+
+ /*
+ * Disabled.
+ *
+ */
+ private FileSystemUtils() {
+ // Empty
+ }
+
+ /**
+ * Deletes a directory recursively. The test case will fail if the directory
+ * does not exist or if deletion fails.
+ *
+ * @param aDir
+ * Directory to delete.
+ */
+ public static void deleteDirRecursively(String aDir) {
+ deleteDirRecursively(new File(aDir));
+ }
+
+ /**
+ * Deletes a directory recursively. See {@link #deleteDirRecursively}.
+ *
+ * @param aDir
+ * Directory.
+ */
+ public static void deleteDirRecursively(File aDir) {
+ TestCase.assertTrue(aDir.isDirectory());
+
+ for (File file : aDir.listFiles()) {
+ if (file.isDirectory()) {
+ deleteDirRecursively(file);
+ } else {
+ delete(file);
+ }
+ }
+
+ delete(aDir);
+ }
+
+ /**
+ * Deletes a file or directory. The test case will fail if the file or
+ * directory does not exist or if deletion fails. Deletion of a non-empty
+ * directory will always fail.
+ *
+ * @param aFile
+ * File or directory to delete.
+ */
+ public static void delete(File aFile) {
+ TestCase.assertTrue(aFile.delete());
+ }
+
+ /**
+ * Gets a path relative to a sub project. This utility should be used to
+ * easily access file paths within a subproject without requiring any
+ * specific Eclipse configuration.
+ *
+ * @param aRelativePath
+ * Relative path.
+ * @param aTestClass
+ * Test class.
+ */
+ public static File getPath(String aRelativePath, Class aTestClass) {
+ CodeSource source = aTestClass.getProtectionDomain().getCodeSource();
+ if (source == null) {
+ LOG.warn("Could not obtain path for '" + aRelativePath
+ + "' for class " + aTestClass
+ + ", using relative path as is");
+ return new File(aRelativePath);
+ }
+ URL location = source.getLocation();
+ String protocol = location.getProtocol();
+ if (!protocol.equals("file")) {
+ LOG.warn("protocol is not 'file': " + location);
+ return new File(aRelativePath);
+ }
+
+ String path = location.getPath();
+ try {
+ path = URLDecoder.decode(location.getPath(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore it.. just don't decode
+ LOG.warn("Decoding path failed: '" + location.getPath() + "'", e );
+ }
+
+ return new File(new File(path).getParentFile(), aRelativePath);
+ }
+
+ /**
+ * Ensures that a directory hierarchy exists (recursively if needed). If it
+ * is not possible to create the directory, then the test case will fail.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.exists() && !aDir.isDirectory()) {
+ TestCase.fail("'" + aDir
+ + "' already exists and is not a directory");
+ }
+ if (aDir.exists()) {
+ return;
+ }
+ createDir(aDir.getParentFile());
+ TestCase.assertTrue("Could not create '" + aDir + "'", aDir.mkdir());
+ }
+
+ /**
+ * Gets the test output directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test output directory.
+ */
+ public static File getTestOutputDir(Class aTestClass) {
+ File file = getPath(TEST_OUTPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Gets the test input directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test input directory.
+ */
+ public static File getTestInputDir(Class aTestClass) {
+ File file = getPath(TEST_INPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Creates a directory hierarchy for the output directory of a test class if
+ * needed.
+ *
+ * @param aTestClass
+ * Test class
+ * @return Test directory.
+ */
+ public static File createTestOutputDir(Class aTestClass) {
+ File file = getTestOutputDir(aTestClass);
+ createDir(file);
+ return file;
+ }
+
+ /**
+ * Gets a test output file name. This returns a File object representing the
+ * output file and ensures that the directory where the file will be created
+ * already exists.
+ *
+ * @param aName
+ * Name of the file.
+ * @param aTestClass
+ * Test class.
+ * @return File.
+ */
+ public static File getTestOutputFile(String aName, Class aTestClass) {
+ File file = new File(getTestOutputDir(aTestClass), aName);
+ createDir(file.getParentFile());
+ return file;
+ }
+
+ public static String read(InputStream aIs) throws IOException {
+ try {
+ StringBuffer buffer = new StringBuffer();
+ int c;
+ while ((c = aIs.read()) != -1) {
+ buffer.append((char)c);
+ }
+ return buffer.toString();
+ } finally {
+ aIs.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the stream resource.
+ */
+public class StreamResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ File file = new File(
+ FileSystemUtils.getTestInputDir(StreamResourceTest.class),
+ "myresource.txt");
+ assertTrue(file.canRead());
+ InputStream fileIs = new FileInputStream(file);
+ InputResource resource = new StreamResource(fileIs);
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+
+
+/**
+ * Test resource for locating resources in the classpath.
+ */
+public class TestResource extends FileResource {
+
+ /**
+ * Test class name.
+ * @param aTestClass Test class.
+ * @param aName Name of the file to look for.
+ */
+ public TestResource(Class aTestClass, String aName) {
+ super(getFile(aTestClass, aName));
+ }
+
+ /**
+ * Computes the file path of the file to look for.
+ * @param aClass Test class name.
+ * @param aName Name of the file.
+ * @return File.
+ */
+ private static File getFile(Class aClass, String aName) {
+ File dir = FileSystemUtils.getTestInputDir(aClass);
+ return new File(dir, aName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jmock.Mock;
+import org.jmock.cglib.MockObjectTestCase;
+
+/**
+ * Test of the observer pattern implementation.
+ */
+public class ObservableTest extends MockObjectTestCase {
+
+ private static final int SUBSCRIBER_COUNT = 100;
+
+ private static final String UPDATE = "send";
+
+ private Observable<ObservableTest, String> _observable;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ _observable = new Observable<ObservableTest, String>(this,
+ new DefaultObserverNotifier());
+ }
+
+ /**
+ * Tests subscription and notification of one subscriber.
+ */
+ public void testOneObserver() {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ String message = "hallo";
+ mockObserver.expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+
+ _observable.send(message);
+ _observable.unsubscribe(subscription);
+ assertEquals(0, _observable.getObserverCount());
+
+ _observable.send(message);
+
+ }
+
+ /**
+ * Subscribes many susbcribers and sends notifications to subscribers.
+ * Verifies that unique subscription number are returned. Also verifies that
+ * the correct subscribers are notfied.
+ */
+ public void testManySubscribers() {
+ int nsubscribers = SUBSCRIBER_COUNT;
+ Mock[] mocks = new Mock[nsubscribers];
+
+ List<Long> subscriptions = new ArrayList<Long>();
+ for (int i = 0; i < nsubscribers; i++) {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ mocks[i] = mockObserver;
+ assertTrue(subscriptions.add(subscription));
+ }
+
+ assertEquals(nsubscribers, _observable.getObserverCount());
+
+ String message = "hallo";
+ for (int i = 0; i < nsubscribers; i++) {
+ mocks[i].expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+ }
+
+ _observable.send(message);
+
+ for (int i = nsubscribers / 2; i < nsubscribers; i++) {
+ _observable.unsubscribe(subscriptions.get(i));
+ }
+ assertEquals(nsubscribers - (nsubscribers - nsubscribers / 2),
+ _observable.getObserverCount());
+
+ message = "blabla";
+ for (int i = 0; i < nsubscribers / 2; i++) {
+ mocks[i].expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+ }
+ _observable.send(message);
+ }
+
+ /**
+ * Subscribes and then unsubscribes with a wrong id. Verifies that
+ * IllegalArgumentException is thrown.
+ *
+ */
+ public void testUnsubscribeWithWrongSubscription() {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ try {
+ _observable.unsubscribe(subscription + 1);
+ } catch (IllegalArgumentException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Useful assertions for use in test cases.
+ */
+public final class AssertionUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private AssertionUtils() {
+ // Empty
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aExpected
+ * Expected object array.
+ * @param aActual
+ * Actual object array.
+ */
+ public static void assertEquals(Object[] aExpected, Object[] aActual) {
+ assertEquals("", aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected array.
+ * @param aActual
+ * Actual array.
+ */
+ public static void assertEquals(String aMsg, Object[] aExpected,
+ Object[] aActual) {
+ TestCase.assertEquals(aMsg + ": Array lengths ", aExpected.length,
+ aActual.length);
+
+ for (int i = 0; i < aExpected.length; i++) {
+ TestCase.assertEquals(aMsg + ": Element " + i, aExpected[i],
+ aActual[i]);
+ }
+ }
+
+ /**
+ * Asserts that two objects are equal, and in case the object is an Object[]
+ * delegates to {@link #assertEquals(String, Object[], Object[]).
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static void assertEquals(String aMsg, Object aExpected,
+ Object aActual) {
+ if (aExpected instanceof Object[]) {
+ AssertionUtils.assertEquals(aMsg, (Object[]) aExpected,
+ (Object[]) aActual);
+
+ return;
+ }
+
+ TestCase.assertEquals(aMsg, aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two maps are equal by comparing all keys and by checking
+ * that the values for the same keys are the same.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpectedMap
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static void assertEquals(String aMsg, Map aExpectedMap, Map aActual) {
+ TestCase.assertEquals("Map sizes differ", aExpectedMap.size(), aActual
+ .size());
+
+ Set keys = aExpectedMap.keySet();
+
+ for (Iterator i = keys.iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ TestCase.assertTrue("Map does not containg entry for key:" + key,
+ aActual.containsKey(key));
+ AssertionUtils.assertEquals("Value of key " + key + " of map",
+ aExpectedMap.get(key), aActual.get(key));
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tracks the occurence of certain events in a test environment. Threads in a
+ * test environment tell the event tracker of the occurrence of certain events
+ * using {@link #eventOccurred(Event)}. Test code inspects the events sent by a
+ * thread using {@link #isEventSent(Thread, Event)}.
+ *
+ * A record is kept of every event which is sent. Therefore, the occurrence of a
+ * new event does not erase a previously sent event.
+ *
+ * @param <Event>
+ * Type of event sent from test code. Usually String will be
+ * sufficient. The event type must provide a sensible implementation
+ * of {@link java.lang.Object#equals(java.lang.Object)}.
+ */
+public class EventTracker<Event> {
+
+ private static final Log LOG = LogFactory.getLog(EventTracker.class);
+
+ /**
+ * Map of Thread object to a list of events.
+ */
+ private Map<Thread, List<Event>> _events;
+
+ /**
+ * Constructs the event tracker.
+ *
+ */
+ public EventTracker() {
+ _events = new HashMap<Thread, List<Event>>();
+ }
+
+ /**
+ * Called by a thread to inform that an event has occurred.
+ *
+ * @param aEvent
+ * Event that was sent.
+ */
+ public synchronized void eventOccurred(Event aEvent) {
+ LOG.info("Event '" + aEvent + "' sent.");
+ Thread current = Thread.currentThread();
+ List<Event> events = _events.get(current);
+ if (events == null) {
+ events = new ArrayList<Event>();
+ _events.put(current, events);
+ }
+ events.add(aEvent);
+ }
+
+ /**
+ * Checks if a specific event has happened in a specific thread.
+ *
+ * @param aThread
+ * Thread to check.
+ * @param aEvent
+ * Event to check for.
+ * @return Whether or not the event was sent.
+ */
+ public synchronized boolean isEventSent(Thread aThread, Event aEvent) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ return false;
+ }
+ return events.contains(aEvent);
+ }
+
+ /**
+ * Gets the events for a thread in the order they were sent
+ *
+ * @param aThread
+ * Thread to get events for.
+ * @return Events that were sent. A zero-sized array is returned if no
+ * events were sent.
+ */
+ public synchronized List<Event> getEvents(Thread aThread) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ events = Collections.emptyList();
+ }
+ return Collections.unmodifiableList(events);
+ }
+
+ /**
+ * Gets the number of times an event was sent summed up
+ * over all threads.
+ *
+ * @param aEvent
+ * Event to check.
+ * @return Number of times it was reached.
+ */
+ public synchronized int getEventCount(Event aEvent) {
+ int count = 0;
+ for (Thread thread : _events.keySet()) {
+ List<Event> events = _events.get(thread);
+ for (Event event : events) {
+ if (event.equals(aEvent)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+
+/**
+ * Exporting the hibernate mapping.
+ */
+public final class HibernateExporter {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateExporter() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(aArgs[1]);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaExport export = new SchemaExport(conf);
+ export.setDelimiter(";");
+ export.setOutputFile(file);
+ export.create(true, false);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaUpdate;
+
+/**
+ * Exporting the hibernate mapping.
+ */
+public final class HibernateUpdater {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateUpdater() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(file);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaUpdate lSchemaUpdate = new SchemaUpdate(conf);
+ lSchemaUpdate.execute(true, true);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.oro.io.AwkFilenameFilter;
+import org.hibernate.cfg.Configuration;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * Hibernate utilities.
+ */
+public final class HibernateUtils {
+
+ private static final String DATABASE_PROPS = "test.database.properties";
+
+ /**
+ * Disabled.
+ *
+ */
+ private HibernateUtils() {
+ // Empty
+ }
+
+ /**
+ * @param aDir
+ * @return
+ */
+ public static Configuration getConfiguration(File aDir) throws IOException {
+ Configuration conf = new Configuration();
+ File[] files = aDir.listFiles((FileFilter) (new AwkFilenameFilter(
+ ".*\\.hbm\\.xml")));
+ for (File f : files) {
+ System.out.println("Mapping file: " + f);
+ conf.addFile(f);
+ }
+
+ Map<String, String> dbProps = getHibernateProperties();
+
+ for (Map.Entry<String, String> entry : dbProps.entrySet()) {
+ System.out.println("Property: " + entry.getKey() + "="
+ + entry.getValue());
+ conf.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ return conf;
+ }
+
+ private static Map<String, String> getHibernateProperties()
+ throws IOException {
+
+ System.out.println("Reading properties file: " + DATABASE_PROPS);
+ InputResource lPropFile = new ClassPathResource(DATABASE_PROPS);
+ Properties props = new Properties();
+ props.load(lPropFile.getInputStream());
+
+ Map<String, String> result = new TreeMap<String, String>();
+ result.put("hibernate.connection.driver_class", props
+ .getProperty("database.driver"));
+ result.put("hibernate.connection.url", props
+ .getProperty("database.url"));
+ result.put("hibernate.connection.username", props
+ .getProperty("database.username"));
+ result.put("hibernate.connection.password", props
+ .getProperty("database.password"));
+
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Spring configuration files to use.
+ */
+public class SpringConfigFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of Spring config files.
+ *
+ */
+ public SpringConfigFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public SpringConfigFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.operation.DatabaseOperation;
+import org.hibernate.SessionFactory;
+import org.jmock.cglib.MockObjectTestCase;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ * Test case support class for spring tests.
+ */
+public class SpringTestCase extends MockObjectTestCase {
+
+ private static final Log LOG = LogFactory.getLog(SpringTestCase.class);
+
+ /**
+ * Session factory bean name.
+ */
+ private static final String SESSION_FACTORY = "sessionFactory";
+
+ /**
+ * Data source bean name.
+ */
+ private static final String DATA_SOURCE = "dataSource";
+
+ /**
+ * Transaction manager bean name.
+ */
+ private static final String TRANSACTION_MANAGER = "transactionManager";
+
+ /**
+ * Name of the ConfigFileList bean that describes the Hibernate mapping
+ * files to use.
+ */
+ private static final String HIBERNATE_CONFIG_FILES = "hibernateMappingFiles";
+
+ /**
+ * Schema pattern.
+ */
+ private static final String SCHEMA_PATTERN = "%";
+
+ /**
+ * List of (String) configuration file locations for spring.
+ */
+ private String[] _configLocations;
+
+ /**
+ * Application context for storing bean definitions that vary on a test by
+ * test basis and cannot be hardcoded in the spring configuration files.
+ */
+ private GenericApplicationContext _parentContext;
+
+ /**
+ * Cached spring application context.
+ */
+ private ApplicationContext _context;
+
+ public SpringTestCase(Class<? extends SpringConfigFiles> aSpringFiles,
+ Class<? extends HibernateMappingFiles> aMappingFiles) {
+ try {
+ SpringConfigFiles springFiles = aSpringFiles.newInstance();
+ _configLocations = springFiles.toArray(new String[0]);
+ } catch (Exception e) {
+ fail("Could not construct spring config files class '"
+ + aSpringFiles.getName() + "'");
+ }
+
+ // Register the Hibernate mapping files as a bean.
+ _parentContext = new GenericApplicationContext();
+ BeanDefinition lDefinition = new RootBeanDefinition(aMappingFiles);
+ _parentContext.registerBeanDefinition(HIBERNATE_CONFIG_FILES,
+ lDefinition);
+ _parentContext.refresh();
+
+ }
+
+ /**
+ * Gets the spring context.
+ *
+ * @return Spring context.
+ */
+ protected synchronized ApplicationContext getSpringContext() {
+ if (_context == null) {
+ _context = new ClassPathXmlApplicationContext(
+ (String[]) _configLocations, _parentContext);
+ assertNotNull(_context);
+ }
+ return _context;
+ }
+
+ /**
+ * @return Hibernate session factory.
+ */
+ protected SessionFactory getSessionFactory() {
+ SessionFactory factory = (SessionFactory) getSpringContext().getBean(
+ SESSION_FACTORY);
+ assertNotNull(factory);
+ return factory;
+ }
+
+ protected void setUp() throws Exception {
+ LOG.info("Performing setUp()");
+
+ super.setUp();
+
+ _context = null; // make sure we get a new application context for
+ // every
+ // new test.
+
+ BeanKernel.overrideBeanFactory(new TestSpringBeanFactory(
+ getSpringContext()));
+
+ cleanDatabase();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ super.tearDown();
+ } finally {
+ LOG.info("tearDown() complete");
+ }
+ }
+
+ /**
+ * @return Transaction manager
+ */
+ protected PlatformTransactionManager getTransactionManager() {
+ PlatformTransactionManager manager = (PlatformTransactionManager) getSpringContext()
+ .getBean(TRANSACTION_MANAGER);
+ assertNotNull(manager);
+ return manager;
+ }
+
+ /**
+ * @return Starts a new transaction.
+ */
+ protected TransactionStatus getTransaction() {
+ DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+ def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+ return getTransactionManager().getTransaction(def);
+ }
+
+ /**
+ * Returns the hibernate template for executing hibernate-specific
+ * functionality.
+ *
+ * @return Hibernate template.
+ */
+ protected HibernateTemplate getTemplate() {
+ HibernateTemplate template = (HibernateTemplate) getSpringContext()
+ .getBean(HibernateTemplate.class.getName());
+ assertNotNull(template);
+ return template;
+ }
+
+ /**
+ * Flushes the session. Should be called after some Hibernate work and
+ * before JDBC is used to check results.
+ *
+ */
+ protected void flush() {
+ getTemplate().flush();
+ }
+
+ /**
+ * Flushes the session first and then removes all objects from the Session
+ * cache. Should be called after some Hibernate work and before JDBC is used
+ * to check results.
+ *
+ */
+ protected void clear() {
+ flush();
+ getTemplate().clear();
+ }
+
+ /**
+ * Evicts the object from the session. This is essential for the
+ * implementation of unit tests where first an object is saved and is
+ * retrieved later. By removing the object from the session, Hibernate must
+ * retrieve the object again from the database.
+ *
+ * @param aObject
+ */
+ protected void evict(Object aObject) {
+ getTemplate().evict(aObject);
+ }
+
+ /**
+ * Gets the connection.
+ *
+ * @return Connection.
+ */
+ public Connection getConnection() {
+ return DataSourceUtils.getConnection(getDataSource());
+ }
+
+ public void cleanDatabase() throws SQLException {
+
+ if (!isDatabaseConfigured()) {
+ return;
+ }
+
+ String[] tables = getTableNames();
+
+ try {
+ IDatabaseConnection connection = new DatabaseConnection(
+ getConnection());
+ ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
+ IDataSet dataset = new FilteredDataSet(filter, connection
+ .createDataSet(tables));
+
+ DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+ } catch (DatabaseUnitException e) {
+ SQLException exc = new SQLException(e.getMessage());
+ exc.initCause(e);
+ throw exc;
+ }
+ }
+
+ /**
+ * @throws SQLException
+ */
+ public String[] getTableNames() throws SQLException {
+
+ List<String> result = new ArrayList<String>();
+ LOG.debug("Getting database table names to clean (schema: '"
+ + SCHEMA_PATTERN + "'");
+
+ ResultSet tables = getConnection().getMetaData().getTables(null,
+ SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+ while (tables.next()) {
+ String table = tables.getString("TABLE_NAME");
+ // Make sure we do not touch hibernate's specific
+ // infrastructure tables.
+ if (!table.toLowerCase().startsWith("hibernate")) {
+ result.add(table);
+ LOG.debug("Adding " + table
+ + " to list of tables to be cleaned.");
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTables(List aTableList) throws SQLException {
+ Iterator liTable = aTableList.iterator();
+ while (liTable.hasNext()) {
+ emptyTable((String) liTable.next());
+ }
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTable(String aTable) throws SQLException {
+ executeSql("delete from " + aTable);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void dropTable(String aTable) throws SQLException {
+ executeQuery("drop table " + aTable);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction.
+ *
+ * @param aSql
+ * SQL statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql) {
+ return executeSql(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL statement.
+ * @param aArg
+ * Argument of the sql statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql, final Object aArg) {
+ return executeSql(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes an sql statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL query to execute.
+ * @param aArgs
+ * Arguments.
+ * @return Number of rows updated.
+ */
+ public int executeSql(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ JdbcTemplate template = new JdbcTemplate(getDataSource());
+ int result = template.update(aSql, aArgs);
+
+ Map<String, Integer> map = new TreeMap<String, Integer>();
+ map.put("result", new Integer(result));
+
+ return map;
+ }
+ });
+
+ return ((Integer) results.get("result")).intValue();
+ }
+
+ /**
+ * Executes a transaction with a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work.
+ * @return Result.
+ */
+ public Object executeTransaction(TransactionCallback aCallback) {
+ TransactionTemplate lTemplate = new TransactionTemplate(
+ getTransactionManager());
+ return lTemplate.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction without a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work. .
+ */
+ protected void executeTransaction(TransactionCallbackWithoutResult aCallback) {
+ TransactionTemplate template = new TransactionTemplate(
+ getTransactionManager());
+ template.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ * @return Result.
+ */
+ public Map executeTransaction(final TestTransactionCallback aCallback) {
+ return (Map) executeTransaction(new TransactionCallback() {
+ public Object doInTransaction(TransactionStatus aArg) {
+ try {
+ return aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ */
+ public void executeTransaction(
+ final TestTransactionCallbackWithoutResult aCallback) {
+ executeTransaction(new TransactionCallbackWithoutResult() {
+ public void doInTransactionWithoutResult(TransactionStatus aArg) {
+ try {
+ aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes an SQL query within a transaction.
+ *
+ * @param aSql
+ * Query to execute.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql) {
+ return executeQuery(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes a query with a single argument. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Query.
+ * @param aArg
+ * Argument.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql, Object aArg) {
+ return executeQuery(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes a query within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Sql query.
+ * @param aArgs
+ * Arguments to the query.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ Connection connection = getConnection();
+
+ PreparedStatement statement = connection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ ResultSet resultSet = statement.executeQuery();
+ TreeMap results = new TreeMap();
+ results.put("resultSet", resultSet);
+
+ return results;
+ }
+ });
+
+ return (ResultSet) results.get("resultSet");
+ }
+
+ /**
+ * Sets the values of a prepared statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aArgs
+ * Arguments to the prepared statement.
+ * @param aStatement
+ * Prepared statement
+ * @throws SQLException
+ */
+ private void setPreparedParams(final Object[] aArgs,
+ PreparedStatement aStatement) throws SQLException {
+ for (int i = 1; i <= aArgs.length; i++) {
+ setPreparedParam(i, aStatement, aArgs[i - 1]);
+ }
+ }
+
+ /**
+ * Sets a prepared statement parameter.
+ *
+ * @param aIndex
+ * Index of the parameter.
+ * @param aStatement
+ * Prepared statement.
+ * @param aObject
+ * Value Must be of type Integer, Long, or String. TODO extend
+ * with more types of values.
+ * @throws SQLException
+ */
+ private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+ Object aObject) throws SQLException {
+ if (aObject instanceof Integer) {
+ aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+ } else if (aObject instanceof Long) {
+ aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+ } else if (aObject instanceof String) {
+ aStatement.setString(aIndex, (String) aObject);
+ } else {
+ TestCase.fail("Unsupported object type for prepared statement: "
+ + aObject.getClass() + " value: " + aObject
+ + " statement: " + aStatement);
+ }
+ }
+
+ private boolean isDatabaseConfigured() {
+ try {
+ getDataSource();
+ } catch (NoSuchBeanDefinitionException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return Returns the dataSource.
+ */
+ public DataSource getDataSource() {
+ DataSource ds = (DriverManagerDataSource) getSpringContext().getBean(
+ DATA_SOURCE);
+ assertNotNull(ds);
+ return ds;
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ protected int getTableSize(String aTable) throws SQLException {
+ ResultSet resultSet = executeQuery("select * from " + aTable);
+ int count = 0;
+
+ while (resultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+ protected int countResultSet(ResultSet aResultSet) throws SQLException {
+ int count = 0;
+
+ while (aResultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.general.BeanFactoryException;
+
+/**
+ * Bean factory which uses Spring.
+ */
+public class TestSpringBeanFactory implements BeanFactory {
+
+ private ApplicationContext _context;
+
+ public TestSpringBeanFactory(ApplicationContext aContext) {
+ _context = aContext;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String, java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _context.getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import junit.framework.Assert;
+
+/**
+ * @author Erik Test support utility.
+ */
+public final class TestSupport {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TestSupport() {
+ // Empty
+ }
+
+ /**
+ * Obtain root directory of JUnit tests.
+ *
+ * @return Directory name.
+ */
+ public static File getTestRootDir() {
+ return new File("testdata");
+ }
+
+ /**
+ * Returns a temporary directory.
+ *
+ * @return Temporary directory.
+ */
+ public static File getTmpDir() {
+ return new File(getTestRootDir(), "tmpdir");
+ }
+
+ /**
+ * Recursively remove a directory.
+ *
+ * @param aSrc
+ * Directoryto remove.
+ */
+ public static void removeDir(File aSrc) {
+ if (!aSrc.exists()) {
+ return;
+ }
+ Assert.assertTrue(aSrc.getPath(), aSrc.isDirectory());
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ removeDir(file);
+ } else {
+ Assert.assertTrue(file.getPath(), file.delete());
+ }
+ }
+ Assert.assertTrue(aSrc.getPath(), aSrc.delete());
+ }
+
+ /**
+ * Recursively copy a directory.
+ *
+ * @param aSrc
+ * Source directory
+ * @param aTarget
+ * Target directory.
+ */
+ public static void copyDir(File aSrc, File aTarget) {
+ Assert.assertTrue(aSrc.isDirectory());
+ Assert.assertTrue(!aTarget.exists());
+
+ aTarget.mkdirs();
+
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ if (!file.getName().equals(".svn")) {
+ copyDir(new File(aSrc, file.getName()), new File(aTarget,
+ file.getName()));
+ }
+ } else {
+ copyFile(file, new File(aTarget, file.getName()));
+ }
+ }
+ }
+
+ /**
+ * Copy a file. If copying fails then the testcase will fail.
+ *
+ * @param aSrc
+ * Source file.
+ * @param aTarget
+ * Target file.
+ */
+ public static void copyFile(File aSrc, File aTarget) {
+
+ try {
+ FileInputStream fis = new FileInputStream(aSrc);
+ FileOutputStream fos = new FileOutputStream(aTarget);
+ FileChannel fcin = fis.getChannel();
+ FileChannel fcout = fos.getChannel();
+
+ // map input file
+
+ MappedByteBuffer mbb = fcin.map(FileChannel.MapMode.READ_ONLY, 0,
+ fcin.size());
+
+ // do the file copy
+ fcout.write(mbb);
+
+ // finish up
+
+ fcin.close();
+ fcout.close();
+ fis.close();
+ fos.close();
+ } catch (IOException e) {
+ Assert.assertTrue("Copying file " + aSrc.getPath() + " to "
+ + aTarget.getPath() + " failed.", false);
+ }
+ }
+
+ /**
+ * Remove a file or directory. The test case will fail if this does not
+ * succeed.
+ *
+ * @param aFile
+ * entry to remove.
+ */
+ public static void delete(File aFile) {
+ Assert
+ .assertTrue("Could not delete " + aFile.getPath(), aFile
+ .delete());
+ }
+
+ /**
+ * Remove all files within a given directory including the directory itself.
+ * This only attempts to remove regular files and not directories within the
+ * directory. If the directory contains a nested directory, the deletion
+ * will fail. The test case will fail if this fails.
+ *
+ * @param aDir
+ * Directory to remove.
+ */
+ public static void deleteDir(File aDir) {
+ cleanDir(aDir);
+ delete(aDir);
+ }
+
+ /**
+ * Remove all regular files within a given directory.
+ *
+ * @param outputDirName
+ */
+ public static void cleanDir(File aDir) {
+ if (!aDir.exists()) {
+ return; // nothing to do.
+ }
+ File[] entries = aDir.listFiles();
+ for (int i = 0; i < entries.length; i++) {
+ File file = entries[i];
+ if (file.isFile()) {
+ Assert.assertTrue("Could not delete " + entries[i].getPath(),
+ entries[i].delete());
+ }
+ }
+ }
+
+ /**
+ * Creates directory if it does not already exist. The test case will fail
+ * if the directory cannot be created.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.isDirectory()) {
+ return; // nothing to do.
+ }
+ Assert.assertTrue("Could not create directory " + aDir.getPath(), aDir
+ .mkdirs());
+ }
+}
--- /dev/null
+package org.wamblee.test;
+
+import java.util.Map;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ */
+public interface TestTransactionCallback {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @return A map containg the resuls of the execution. This is a convenient
+ * method of returning multiple results from a call.
+ */
+ Map execute() throws Exception;
+}
--- /dev/null
+package org.wamblee.test;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ */
+public interface TestTransactionCallbackWithoutResult {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ */
+ void execute() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import junit.framework.TestCase;
+
+/**
+ * Timing utilities.
+ */
+public final class TimingUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TimingUtils() {
+ // Empty
+ }
+
+ /**
+ * Sleeps for a time.
+ * @param aMillis Number of milliseconds to sleep.
+ */
+ public static void sleep(int aMillis) {
+ try {
+ Thread.sleep(aMillis);
+ } catch (InterruptedException e) {
+ TestCase.fail("Who interrupted my sleep?");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.io.ClassPathResource;
+import org.wamblee.io.FileSystemUtils;
+
+/**
+ * Tests for {@link org.wamblee.xml.ClasspathUriResolver}.
+ */
+public class ClasspathUriResolverTest extends TestCase {
+
+ private URIResolver _resolver;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _resolver = new ClasspathUriResolver();
+ }
+
+ /**
+ * Resolves an existing file. Verifies the file is resolved correctly.
+ * @throws TransformerException
+ * @throws IOException
+ */
+ public void testResolveExistingFile() throws TransformerException, IOException {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml.xsl", "");
+ assertTrue(source instanceof StreamSource);
+ String resolved = FileSystemUtils.read(((StreamSource)source).getInputStream());
+
+ ClassPathResource resource = new ClassPathResource("org/wamblee/xml/reportToHtml.xsl");
+ String expected = FileSystemUtils.read(resource.getInputStream());
+ assertEquals(expected, resolved);
+ }
+
+ /**
+ * Resolves a non-existing file. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testResolveNonExistingFile() {
+ try {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml-nonexisting.xsl", "");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import junit.framework.TestCase;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.Element;
+
+/**
+ * XML test support utilities.
+ */
+public final class XmlUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private XmlUtils() {
+ // Empty
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg,
+ org.w3c.dom.Document aExpected, org.w3c.dom.Document aActual) {
+ assertEquals(aMsg, DomUtils.convert(aExpected), DomUtils
+ .convert(aActual));
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Document aExpected,
+ Document aActual) {
+ assertEquals(aMsg + "/" + aExpected.getRootElement().getName(), aExpected.getRootElement(), aActual.getRootElement());
+ }
+
+ /**
+ * Checks equality of two XML elements excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Element aExpected,
+ Element aActual) {
+
+ // Name.
+ TestCase.assertEquals(aMsg + "/name()", aExpected.getName(), aActual
+ .getName());
+
+ // Text
+ TestCase.assertEquals(aMsg + "/text()", aExpected.getTextTrim(),
+ aActual.getTextTrim());
+
+ // Attributes
+ List<Attribute> expectedAttrs = aExpected.attributes();
+ Collections.sort(expectedAttrs, new AttributeComparator());
+ List<Attribute> actualAttrs = aActual.attributes();
+ Collections.sort(actualAttrs, new AttributeComparator());
+
+ TestCase.assertEquals("count(" + aMsg + "/@*)", expectedAttrs.size(),
+ actualAttrs.size());
+ for (int i = 0; i < expectedAttrs.size(); i++) {
+ String msg = aMsg + "/@" + expectedAttrs.get(i).getName();
+ assertEquals(msg, expectedAttrs.get(i), actualAttrs.get(i));
+ }
+
+ // Nested elements.
+ List<Element> expectedElems = aExpected.elements();
+ List<Element> actualElems = aActual.elements();
+ TestCase.assertEquals("count(" + aMsg + "/*)", expectedElems.size(),
+ actualElems.size());
+ // determine the how-manyth element of the given name we are at.
+ // Maps element name to the last used index (or null if not yet used)
+ Map<String, Integer> elementIndex = new TreeMap<String, Integer>();
+ for (int i = 0; i < expectedElems.size(); i++) {
+ String elemName = expectedElems.get(i).getName();
+ Integer index = elementIndex.get(elemName);
+ if (index == null) {
+ index = 1;
+ } else {
+ index++;
+ }
+ elementIndex.put(elemName, index);
+ String msg = aMsg + "/" + expectedElems.get(i).getName() + "["
+ + index + "]";
+
+ assertEquals(msg, expectedElems.get(i), actualElems.get(i));
+ }
+ }
+
+ /**
+ * Checks equality of two attributes. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Attribute aExpected,
+ Attribute aActual) {
+ TestCase.assertEquals(aMsg + ":name", aExpected.getName(), aActual
+ .getName());
+ TestCase.assertEquals(aMsg + ":value", aExpected.getValue(), aActual
+ .getValue());
+ }
+
+ /**
+ * Comparator which compares attributes by name.
+ */
+ private static final class AttributeComparator implements
+ Comparator<Attribute> {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Comparator#compare(T, T)
+ */
+ public int compare(Attribute aAttribute1, Attribute aAttribute2) {
+ return aAttribute1.getName().compareTo(aAttribute2.getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.io.ClassPathResource;
+import org.w3c.dom.Document;
+import org.wamblee.io.FileSystemUtils;
+import org.wamblee.io.InputResource;
+import org.wamblee.io.TestResource;
+
+/**
+ * Tests the XSL transformer.
+ */
+public class XslTransformerTest extends TestCase {
+
+ private static final String REPORT_XML = "report.xml";
+
+ private static final String REPORT_TO_HTML_XSLT = "reportToHtml.xsl";
+
+ private static final String REPORT_TO_HTML2_XSLT = "reportToHtml2.xsl";
+
+ private static final String REPORT_TO_HTML_INVALID_XSLT = "reportToHtml-invalid.xsl";
+
+ private static final String REPORT_TO_HTML_NONWELLFORMED_XSLT = "reportToHtml-nonwellformed.xsl";
+
+ private static final String REPORT_TO_TEXT_XSLT = "reportToText.xsl";
+
+
+ /**
+ * Transforms a file while using the default resolver, where the included
+ * file can be found. Verifies the transformation is done correctly.
+ *
+ */
+ public void testTransformUsingDefaultResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(
+ new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ Document document = builder.parse(xmlResource.getInputStream());
+ Source documentSource = new StreamSource(xmlResource.getInputStream());
+
+ Document expected = DomUtils.read(new TestResource(
+ XslTransformerTest.class, "output-reportToHtml-report.xml")
+ .getInputStream());
+
+ Document output1 = transformer.transform(documentData, xslt);
+ XmlUtils.assertEquals("byte[] transform", expected, output1);
+
+ Document output2 = transformer.transform(document, xslt);
+ XmlUtils.assertEquals("document transform", expected, output2);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Result output = new StreamResult(os);
+ transformer.transform(documentSource, output, xslt);
+ XmlUtils.assertEquals("document source transform", expected, DomUtils
+ .read(os.toString()));
+
+ String result = transformer.textTransform(documentData, xslt);
+ XmlUtils
+ .assertEquals("text transform", expected, DomUtils.read(result));
+ }
+
+ /**
+ * Transforms a file using the default resolver where the included file
+ * cannot be found. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testTransformUsingDefaultResolverFails() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML2_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using an invalid Xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformInvalidXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML_INVALID_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a non-well formed xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformNonWellformedXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML_NONWELLFORMED_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a class path resolver.
+ *
+ */
+ public void testTransformUsingClassPathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_HTML2_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ Document output1 = transformer.transform(documentData, xslt);
+ Document expected = DomUtils.read(new TestResource(
+ XslTransformerTest.class, "output-reportToHtml-report.xml")
+ .getInputStream());
+ XmlUtils.assertEquals("doc", expected, output1);
+ }
+
+ /**
+ * Transforms a file to text output. Verifies the file is transformed
+ * correctly.
+ *
+ */
+ public void testTransformToTextOutput() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new TestResource(XslTransformerTest.class,
+ REPORT_XML);
+ Source xslt = new StreamSource(
+ new File(FileSystemUtils
+ .getTestInputDir(XslTransformerTest.class),
+ REPORT_TO_TEXT_XSLT));
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ String result = transformer.textTransform(documentData, xslt);
+ String expected = "Hello world!";
+ assertEquals("text transform", expected, result);
+ }
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)}.
+ *
+ */
+ public void testResolveWithDefaultResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer();
+ File utilities = new File(FileSystemUtils.getTestInputDir(XslTransformerTest.class), "utilities.xsl");
+ Source source = transformer.resolve(utilities.getAbsolutePath());
+ assert(source instanceof StreamSource);
+ StreamSource ssource = (StreamSource)source;
+ String data = FileSystemUtils.read(ssource.getInputStream());
+ String expected = FileSystemUtils.read(new ClassPathResource("org/wamblee/xml/utilities.xsl").getInputStream());
+ assertEquals(expected, data);
+ }
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver where the file does not exist.
+ *
+ */
+ public void testResolveWithDefaultResolverFileNotFound() {
+ XslTransformer transformer = new XslTransformer();
+ try {
+ Source source = transformer.resolve("org/wamblee/xml/utilities-nonexistent.xsl");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver.
+ *
+ */
+ public void testResolveWithClasspathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+ Source source = transformer.resolve("org/wamblee/xml/utilities.xsl");
+ assert(source instanceof StreamSource);
+ StreamSource ssource = (StreamSource)source;
+ String data = FileSystemUtils.read(ssource.getInputStream());
+ String expected = FileSystemUtils.read(new ClassPathResource("org/wamblee/xml/utilities.xsl").getInputStream());
+ assertEquals(expected, data);
+ }
+
+}
+
+
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry path="support/src/main/java" kind="src"/>
+ <classpathentry path="support/src/test/java" kind="src"/>
+ <classpathentry excluding="**" path="support/src/main/resources" output="support/src/main/resources" kind="src"/>
+ <classpathentry excluding="**" path="support/src/test/resources" output="support/src/test/resources" kind="src"/>
+ <classpathentry path="socketproxy/src/main/java" kind="src"/>
+ <classpathentry path="socketproxy/src/test/java" kind="src"/>
+ <classpathentry excluding="**" path="socketproxy/src/test/resources" output="socketproxy/src/test/resources" kind="src"/>
+ <classpathentry path="crawler/basic/src/main/java" kind="src"/>
+ <classpathentry path="crawler/basic/src/test/java" kind="src"/>
+ <classpathentry excluding="**" path="crawler/basic/src/test/resources" output="crawler/basic/src/test/resources" kind="src"/>
+ <classpathentry path="crawler/kiss/src/main/java" kind="src"/>
+ <classpathentry path="crawler/kiss/src/test/java" kind="src"/>
+ <classpathentry excluding="**" path="crawler/kiss/src/main/resources" output="crawler/kiss/src/main/resources" kind="src"/>
+ <classpathentry excluding="**" path="crawler/kiss/src/test/resources" output="crawler/kiss/src/test/resources" kind="src"/>
+ <classpathentry path="crawler/kissweb/src/main/java" kind="src"/>
+ <classpathentry path="crawler/kissweb/src/test/java" kind="src"/>
+ <classpathentry excluding="**" path="crawler/kissweb/src/main/resources" output="crawler/kissweb/src/main/resources" kind="src"/>
+ <classpathentry excluding="**" path="crawler/kissweb/src/test/resources" output="crawler/kissweb/src/test/resources" kind="src"/>
+ <classpathentry path="gps/src/main/java" kind="src"/>
+ <classpathentry path="gps/src/test/java" kind="src"/>
+ <classpathentry excluding="**" path="gps/src/test/resources" output="gps/src/test/resources" kind="src"/>
+ <classpathentry path="mythtv/src/main/java" kind="src"/>
+ <classpathentry excluding="**" path="mythtv/src/main/resources" output="mythtv/src/main/resources" kind="src"/>
+ <classpathentry path="org.eclipse.jdt.launching.JRE_CONTAINER" kind="con"/>
+ <classpathentry path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER" kind="con"/>
+ <classpathentry path="crawler/basic/build/bin" kind="output"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project-module
+ type="WEB"
+ name="utils"
+ id="myeclipse.1145729924792"
+ context-root="/kisscrawler"
+ j2ee-spec="1.4"
+ archive="utils.war">\r
+ <attributes>\r
+ <attribute name="webrootdir" value="/crawler/kissweb/src/webapp" />\r
+ </attributes>\r
+</project-module>\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>utils</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.WebClasspathBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.J2EEProjectValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.j2eedt.core.DeploymentDescriptorValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.ibm.etools.validation.validationbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.genuitec.eclipse.ast.deploy.core.DeploymentBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.ibm.sse.model.structuredbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.maven.ide.eclipse.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.maven.ide.eclipse.maven2Nature</nature>
+ <nature>com.genuitec.eclipse.ast.deploy.core.deploymentnature</nature>
+ <nature>com.genuitec.eclipse.j2eedt.core.webnature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+This is the utilities project of wamblee.org. It contains various utilities
+and useful programs for various purposes. The most important part is the
+support library.
+
--- /dev/null
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
--- /dev/null
+This project using maven2. Since maven is all about conventions, see the
+maven site http://maven.apache.org for information on how to build this project.
+
+
+You must use a java 5 compiler, jdk 1.4.* or earlier will not work.
+
--- /dev/null
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">\r
+<xsl:output method="html" indent="yes"/>\r
+<xsl:decimal-format decimal-separator="." grouping-separator="," />\r
+\r
+<!-- Checkstyle XML Style Sheet by Stephane Bailliez <sbailliez@apache.org> -->\r
+<!-- Part of the Checkstyle distribution found at http://checkstyle.sourceforge.net -->\r
+<!-- Usage (generates checkstyle_report.html): -->\r
+<!-- <checkstyle failonviolation="false" config="${check.config}"> -->\r
+<!-- <fileset dir="${src.dir}" includes="**/*.java"/> -->\r
+<!-- <formatter type="xml" toFile="${doc.dir}/checkstyle_report.xml"/> -->\r
+<!-- </checkstyle> -->\r
+<!-- <style basedir="${doc.dir}" destdir="${doc.dir}" -->\r
+<!-- includes="checkstyle_report.xml" -->\r
+<!-- style="${doc.dir}/checkstyle-noframes.xsl"/> -->\r
+\r
+<xsl:template match="checkstyle">\r
+ <html>\r
+ <head>\r
+ <style type="text/css">\r
+ .bannercell {\r
+ border: 0px;\r
+ padding: 0px;\r
+ }\r
+ body {\r
+ margin-left: 10;\r
+ margin-right: 10;\r
+ font:normal 80% arial,helvetica,sanserif;\r
+ background-color:#FFFFFF;\r
+ color:#000000;\r
+ }\r
+ .a td { \r
+ background: #efefef;\r
+ }\r
+ .b td { \r
+ background: #fff;\r
+ }\r
+ th, td {\r
+ text-align: left;\r
+ vertical-align: top;\r
+ }\r
+ th {\r
+ font-weight:bold;\r
+ background: #ccc;\r
+ color: black;\r
+ }\r
+ table, th, td {\r
+ font-size:100%;\r
+ border: none\r
+ }\r
+ table.log tr td, tr th {\r
+ \r
+ }\r
+ h2 {\r
+ font-weight:bold;\r
+ font-size:140%;\r
+ margin-bottom: 5;\r
+ }\r
+ h3 {\r
+ font-size:100%;\r
+ font-weight:bold;\r
+ background: #525D76;\r
+ color: white;\r
+ text-decoration: none;\r
+ padding: 5px;\r
+ margin-right: 2px;\r
+ margin-left: 2px;\r
+ margin-bottom: 0;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <a name="top"></a>\r
+ <!-- jakarta logo -->\r
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">\r
+ <tr>\r
+ <td class="bannercell" rowspan="2">\r
+ <!--a href="http://jakarta.apache.org/">\r
+ <img src="http://jakarta.apache.org/images/jakarta-logo.gif" alt="http://jakarta.apache.org" align="left" border="0"/>\r
+ </a-->\r
+ </td>\r
+ <td class="text-align:right"><h2>CheckStyle Audit</h2></td>\r
+ </tr>\r
+ <tr>\r
+ <td class="text-align:right">Designed for use with <a href='http://checkstyle.sourceforge.net/'>CheckStyle</a> and <a href='http://jakarta.apache.org'>Ant</a>.</td>\r
+ </tr>\r
+ </table>\r
+ <hr size="1"/>\r
+ \r
+ <!-- Summary part -->\r
+ <xsl:apply-templates select="." mode="summary"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- Package List part -->\r
+ <xsl:apply-templates select="." mode="filelist"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- For each package create its part -->\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:apply-templates select="."/>\r
+ <p/>\r
+ <p/>\r
+ </xsl:for-each>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ \r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+ \r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="filelist"> \r
+ <h3>Files</h3>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Name</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:variable name="errorCount" select="count(error)"/> \r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><a href="#f-{@name}"><xsl:value-of select="@name"/></a></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table> \r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="file">\r
+ <a name="f-{@name}"></a>\r
+ <h3>File <xsl:value-of select="@name"/></h3>\r
+ \r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Error Description</th>\r
+ <th>Line</th>\r
+ </tr>\r
+ <xsl:for-each select="error">\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="@message"/></td>\r
+ <td><xsl:value-of select="@line"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table>\r
+ <a href="#top">Back to top</a>\r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="summary">\r
+ <h3>Summary</h3>\r
+ <xsl:variable name="fileCount" select="count(file)"/>\r
+ <xsl:variable name="errorCount" select="count(file/error)"/>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Files</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="$fileCount"/></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </table>\r
+ </xsl:template>\r
+ \r
+ <xsl:template name="alternated-row">\r
+ <xsl:attribute name="class">\r
+ <xsl:if test="position() mod 2 = 1">a</xsl:if>\r
+ <xsl:if test="position() mod 2 = 0">b</xsl:if>\r
+ </xsl:attribute> \r
+ </xsl:template> \r
+</xsl:stylesheet>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0"?>\r
+\r
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\r
+\r
+<xsl:template match="/">\r
+ <html>\r
+ <head>\r
+ <title>Sun Coding Style Violations</title>\r
+ </head>\r
+ <body bgcolor="#FFFFEF">\r
+ <p><b>Coding Style Check Results</b></p>\r
+ <table border="1" cellspacing="0" cellpadding="2">\r
+ <tr bgcolor="#CC9966">\r
+ <th colspan="2"><b>Summary</b></th>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total files checked</td>\r
+ <td><xsl:number level="any" value="count(descendant::file)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Files with errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::file[error])"/></td>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::error)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Errors per file</td>\r
+ <td><xsl:number level="any" value="count(descendant::error) div count(descendant::file)"/></td>\r
+ </tr>\r
+ </table>\r
+ <hr align="left" width="95%" size="1"/>\r
+ <p>The following are violations of the Sun Coding-Style Standards:</p>\r
+ <p/>\r
+ <xsl:apply-templates/>\r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+\r
+<xsl:template match="file[error]">\r
+ <table bgcolor="#AFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> File: </th>\r
+ <td>\r
+ <xsl:value-of select="@name"/>\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ <table bgcolor="#DFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> Line Number </th>\r
+ <th> Error Message </th>\r
+ </tr>\r
+ <xsl:apply-templates select="error"/>\r
+ </table>\r
+ <p/>\r
+</xsl:template>\r
+\r
+<xsl:template match="error">\r
+ <tr>\r
+ <td>\r
+ <xsl:value-of select="@line"/>\r
+ </td>\r
+ <td>\r
+ <xsl:value-of select="@message"/>\r
+ </td>\r
+ </tr>\r
+</xsl:template>\r
+\r
+</xsl:stylesheet>\r
--- /dev/null
+<?xml version="1.0"?>\r
+<!DOCTYPE module PUBLIC\r
+ "-//Puppy Crawl//DTD Check Configuration 1.2//EN"\r
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">\r
+\r
+<!--\r
+\r
+ Checkstyle configuration that checks the sun coding conventions from:\r
+\r
+ - the Java Language Specification at\r
+ http://java.sun.com/docs/books/jls/second_edition/html/index.html\r
+\r
+ - the Sun Code Conventions at http://java.sun.com/docs/codeconv/\r
+\r
+ - the Javadoc guidelines at\r
+ http://java.sun.com/j2se/javadoc/writingdoccomments/index.html\r
+\r
+ - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html\r
+\r
+ - some best practices\r
+\r
+ Checkstyle is very configurable. Be sure to read the documentation at\r
+ http://checkstyle.sf.net (or in your downloaded distribution).\r
+\r
+ Most Checks are configurable, be sure to consult the documentation.\r
+\r
+ To completely disable a check, just comment it out or delete it from the file.\r
+\r
+ Finally, it is worth reading the documentation.\r
+\r
+-->\r
+\r
+<module name="Checker">\r
+\r
+ <!-- Checks that a package.html file exists for each package. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->\r
+ <!-- module name="PackageHtml"/ -->\r
+\r
+ <!-- Checks whether files end with a new line. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->\r
+ <module name="NewlineAtEndOfFile"/>\r
+\r
+ <!-- Checks that property files contain the same keys. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->\r
+ <module name="Translation"/>\r
+\r
+\r
+ <module name="TreeWalker">\r
+\r
+ <!-- Checks for Javadoc comments. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html -->\r
+ <!-- module name="JavadocMethod"/ -->\r
+ <!-- module name="JavadocType"/ -->\r
+ <!-- module name="JavadocVariable"/ -->\r
+ <!-- module name="JavadocStyle"/ -->\r
+ \r
+ <!-- Checks for Naming Conventions. -->\r
+ <!-- See http://checkstyle.sf.net/config_naming.html -->\r
+ <module name="ConstantName"/>\r
+ <module name="LocalFinalVariableName"/>\r
+ <module name="LocalVariableName"/>\r
+ <module name="MemberName">
+ <property name="format" value="_[a-z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="MethodName" />\r
+ <module name="PackageName"/>\r
+ <module name="ParameterName">
+ <property name="format" value="^a[A-Z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="StaticVariableName">
+ <property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
+ </module>\r
+ <module name="TypeName"/>\r
+\r
+\r
+ <!-- Checks for Headers -->\r
+ <!-- See http://checkstyle.sf.net/config_header.html -->\r
+ <!-- <module name="Header"> -->\r
+ <!-- The follow property value demonstrates the ability -->\r
+ <!-- to have access to ANT properties. In this case it uses -->\r
+ <!-- the ${basedir} property to allow Checkstyle to be run -->\r
+ <!-- from any directory within a project. See property -->\r
+ <!-- expansion, -->\r
+ <!-- http://checkstyle.sf.net/config.html#properties -->\r
+ <!-- <property -->\r
+ <!-- name="headerFile" -->\r
+ <!-- value="${basedir}/java.header"/> -->\r
+ <!-- </module> -->\r
+\r
+ <!-- Following interprets the header file as regular expressions. -->\r
+ <!-- <module name="RegexpHeader"/> -->\r
+\r
+\r
+ <!-- Checks for imports -->\r
+ <!-- See http://checkstyle.sf.net/config_import.html -->\r
+ <module name="AvoidStarImport"/>\r
+ <module name="IllegalImport"/> <!-- defaults to sun.* packages -->\r
+ <module name="RedundantImport"/>\r
+ <module name="UnusedImports"/>\r
+\r
+\r
+ <!-- Checks for Size Violations. -->\r
+ <!-- See http://checkstyle.sf.net/config_sizes.html -->\r
+ <module name="FileLength"/>\r
+ <module name="LineLength">
+ <property name="max" value="120"/>
+ </module>\r
+ <module name="MethodLength"/>\r
+ <module name="ParameterNumber"/>\r
+\r
+\r
+ <!-- Checks for whitespace -->\r
+ <!-- See http://checkstyle.sf.net/config_whitespace.html -->\r
+ <module name="EmptyForIteratorPad"/>\r
+ <!-- module name="MethodParamPad"/ -->\r
+ <!-- module name="NoWhitespaceAfter"/ -->\r
+ <module name="NoWhitespaceBefore"/>\r
+ <module name="OperatorWrap"/>\r
+ <module name="ParenPad"/>\r
+ <!-- module name="TypecastParenPad"/ -->\r
+ <module name="TabCharacter"/>\r
+ <module name="WhitespaceAfter"/>\r
+ <!-- module name="WhitespaceAround">
+ <property name="tokens" value=" ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR,MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,GENERIC_START,GENERIC_END,TYPE_EXTENSION_AND,WILDCARD_TYPE"/>
+ </module -->\r
+\r
+\r
+ <!-- Modifier Checks -->\r
+ <!-- See http://checkstyle.sf.net/config_modifiers.html -->\r
+ <module name="ModifierOrder"/>\r
+ <module name="RedundantModifier"/>\r
+\r
+\r
+ <!-- Checks for blocks. You know, those {}'s -->\r
+ <!-- See http://checkstyle.sf.net/config_blocks.html -->\r
+ <module name="AvoidNestedBlocks">
+ <property name="allowInSwitchCase" value="true"/>
+ </module>\r
+ <module name="EmptyBlock"/>\r
+ <module name="LeftCurly"/>\r
+ <module name="NeedBraces"/>\r
+ <module name="RightCurly"/>\r
+\r
+\r
+ <!-- Checks for common coding problems -->\r
+ <!-- See http://checkstyle.sf.net/config_coding.html -->\r
+ <module name="AvoidInlineConditionals"/>\r
+ <!-- module name="DoubleCheckedLocking"/ --> \r
+ <module name="EmptyStatement"/>\r
+ <module name="EqualsHashCode"/>\r
+ <module name="HiddenField"/>\r
+ <module name="IllegalInstantiation"/>\r
+ <module name="InnerAssignment"/>\r
+ <module name="MagicNumber">
+ <property name="ignoreNumbers" value="-1,0,1,2,3,4"/>
+ </module>\r
+ <module name="MissingSwitchDefault"/>\r
+ <module name="RedundantThrows"/>\r
+ <module name="SimplifyBooleanExpression"/>\r
+ <module name="SimplifyBooleanReturn"/>\r
+ <!-- module name="ExplicitInitialization"/ -->\r
+\r
+ <!-- Checks for class design -->\r
+ <!-- See http://checkstyle.sf.net/config_design.html -->\r
+ <!-- module name="DesignForExtension"/ -->\r
+ <module name="FinalClass"/>\r
+ <module name="HideUtilityClassConstructor"/>\r
+ <module name="InterfaceIsType"/>\r
+ <module name="VisibilityModifier"/>\r
+\r
+\r
+ <!-- Miscellaneous other checks. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html -->\r
+ <module name="ArrayTypeStyle"/>\r
+ <!-- module name="FinalParameters"/ -->\r
+ <!-- module name="GenericIllegalRegexp">\r
+ <property name="format" value="\s+$"/>\r
+ <property name="message" value="Line has trailing spaces."/>\r
+ </module -->\r
+ <module name="TodoComment"/>\r
+ <module name="UpperEll"/>\r
+\r
+ </module>\r
+\r
+</module>\r
--- /dev/null
+This directory contains a generic web crawler (basic directory) and several useful implementations build on top of this.
+
--- /dev/null
+This is a general library for implementing a web crawler.
+
+The crawler works by retrieving an HTML page and transforming the HTML
+(content + presentation) into content using XSLT stylesheets. Using a convention
+for links in the converted content, it becomes possible to build a generic interface on the retrieved pages for navigating through the content.
+
+A configuration file determines how a certain page must be retrieved and transformed.
+
+
--- /dev/null
+
+<target name="crawler.src.d"
+ depends="logging.d,dom4j.d,xerces.d,httpclient.d,jtidy.d,wamblee.support.d">
+</target>
+
+<target name="crawler.test.d" depends="wamblee.support.test.d">
+</target>
+
+
+<property name="crawler.dist.dir" value="${lib.dir}/wamblee/crawler/basic"/>
+<target name="wamblee.crawler.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${crawler.dist.dir}">
+ <include name="wamblee-crawler-basic.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.crawler.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${crawler.dist.dir}">
+ <include name="wamblee-crawler-basic-test.jar"/>
+ </fileset>
+ </copy>
+</target>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-basic</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org Basic crawler framework</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jtidy</groupId>
+ <artifactId>jtidy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=DEBUG
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.w3c.dom.Document;
+import org.w3c.tidy.Tidy;
+import org.wamblee.xml.DomUtils;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * General support claas for all kinds of requests.
+ */
+public abstract class AbstractPageRequest implements PageRequest {
+
+ private static final Log LOG = LogFactory.getLog(AbstractPageRequest.class);
+
+ private static final String REDIRECT_HEADER = "Location";
+
+ private int _maxTries;
+
+ private int _maxDelay;
+
+ private NameValuePair[] _params;
+
+ private NameValuePair[] _headers;
+
+ private String _xslt;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the request.
+ *
+ * @param aMaxTries
+ * Maximum retries to perform.
+ * @param aMaxDelay
+ * Maximum delay before executing a request.
+ * @param aParams
+ * Request parameters to use.
+ * @param aHeaders
+ * Request headers to use.
+ * @param aXslt
+ * XSLT used to convert the response.
+ */
+ protected AbstractPageRequest(int aMaxTries, int aMaxDelay,
+ NameValuePair[] aParams, NameValuePair[] aHeaders, String aXslt, XslTransformer aTransformer) {
+ if (aParams == null) {
+ throw new IllegalArgumentException("aParams is null");
+ }
+ if (aHeaders == null) {
+ throw new IllegalArgumentException("aHeaders is null");
+ }
+ if (aXslt == null) {
+ throw new IllegalArgumentException("aXslt is null");
+ }
+ _maxTries = aMaxTries;
+ _maxDelay = aMaxDelay;
+ _params = aParams;
+ _headers = aHeaders;
+ _xslt = aXslt;
+ _transformer = aTransformer;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#overrideXslt(java.lang.String)
+ */
+ public void overrideXslt(String aXslt) {
+ _xslt = aXslt;
+ }
+
+ /**
+ * Gets the parameters for the request.
+ *
+ * @param aParams Additional parameters to use, obtained from another page, most likely as
+ * hidden form fields.
+ * @return Request parameters.
+ */
+ protected NameValuePair[] getParameters(NameValuePair[] aParams) {
+ List<NameValuePair> params = new ArrayList<NameValuePair>();
+ params.addAll(Arrays.asList(_params));
+ params.addAll(Arrays.asList(aParams));
+ return params.toArray(new NameValuePair[0]);
+ }
+
+ /**
+ * Gets the headers for the request.
+ * @return Request headers.
+ */
+ protected NameValuePair[] getHeaders() {
+ return _headers;
+ }
+
+ /**
+ * Executes the request with a random delay and with a maximum number of
+ * retries.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method representing the request.
+ * @return XML document describing the response.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case transformation of the HTML to XML fails.
+ */
+ protected Document executeMethod(HttpClient aClient, HttpMethod aMethod)
+ throws IOException, TransformerException {
+
+ for (NameValuePair header: getHeaders()) {
+ aMethod.setRequestHeader(header.getName(), header.getValue());
+ }
+
+ int triesLeft = _maxTries;
+ while (triesLeft > 0) {
+ triesLeft--;
+ try {
+ return executeMethodWithoutRetries(aClient, aMethod);
+ } catch (TransformerException e) {
+ if (triesLeft == 0) {
+ throw e;
+ }
+ }
+ }
+ throw new RuntimeException("Code should never reach this point");
+ }
+
+ /**
+ * Executes the request without doing any retries in case XSLT
+ * transformation fails.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method to execute.
+ * @return XML document containing the result.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case transformation of the result to XML fails.
+ */
+ protected Document executeMethodWithoutRetries(HttpClient aClient,
+ HttpMethod aMethod) throws IOException, TransformerException {
+ try {
+ aMethod = executeWithRedirects(aClient, aMethod);
+ byte[] xhtmlData = getXhtml(aMethod);
+
+
+ Document transformed = _transformer.transform(xhtmlData,
+ _transformer.resolve(_xslt));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Transformer transformer = TransformerFactory.newInstance()
+ .newTransformer();
+ transformer.setParameter(OutputKeys.INDENT, "yes");
+ transformer.setParameter(OutputKeys.METHOD, "xml");
+ transformer.transform(new DOMSource(transformed), new StreamResult(
+ os));
+ LOG.debug("Transformed result is \n" + os.toString());
+ return transformed;
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException("Transformer configuration problem", e);
+ } finally {
+ // Release the connection.
+ aMethod.releaseConnection();
+ }
+ }
+
+ /**
+ * Gets the result of the HTTP method as an XHTML document.
+ *
+ * @param aMethod
+ * Method to invoke.
+ * @return XHTML as a byte array.
+ * @throws IOException
+ * In case of problems obtaining the XHTML.
+ */
+ private byte[] getXhtml(HttpMethod aMethod) throws IOException {
+ // Transform the HTML into wellformed XML.
+ Tidy tidy = new Tidy();
+ tidy.setXHTML(true);
+ tidy.setQuiet(true);
+ tidy.setShowWarnings(false);
+
+ // We write the jtidy output to XML since the DOM tree it produces is
+ // not namespace aware and namespace awareness is required by XSLT.
+ // An alternative is to configure namespace awareness of the XML parser
+ // in a system wide way.
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Document w3cDoc = tidy.parseDOM(aMethod.getResponseBodyAsStream(), os);
+ DomUtils.removeDuplicateAttributes(w3cDoc);
+ LOG.debug("Content of response is \n" + os.toString());
+
+ ByteArrayOutputStream xhtml = new ByteArrayOutputStream();
+ XMLSerializer serializer = new XMLSerializer(xhtml, new OutputFormat());
+ serializer.serialize(w3cDoc);
+ xhtml.flush();
+
+ return xhtml.toByteArray();
+ }
+
+ /**
+ * Sleeps for a random time but no more than the maximum delay.
+ *
+ */
+ private void delay() {
+ try {
+ Thread.sleep((long) ((float) _maxDelay * Math.random()));
+ } catch (InterruptedException e) {
+ return; // to satisfy checkstyle
+ }
+ }
+
+ /**
+ * Executes the request and follows redirects if needed.
+ *
+ * @param aClient
+ * HTTP client to use.
+ * @param aMethod
+ * Method to use.
+ * @return Final HTTP method used (differs from the parameter passed in in
+ * case of redirection).
+ * @throws IOException
+ * In case of network problems.
+ */
+ private HttpMethod executeWithRedirects(HttpClient aClient,
+ HttpMethod aMethod) throws IOException {
+ delay();
+ int statusCode = aClient.executeMethod(aMethod);
+
+ switch (statusCode) {
+ case HttpStatus.SC_OK: {
+ return aMethod;
+ }
+ case HttpStatus.SC_MOVED_PERMANENTLY:
+ case HttpStatus.SC_MOVED_TEMPORARILY:
+ case HttpStatus.SC_SEE_OTHER: {
+ aMethod.releaseConnection();
+ Header header = aMethod.getResponseHeader(REDIRECT_HEADER);
+ aMethod = new GetMethod(header.getValue());
+ return executeWithRedirects(aClient, aMethod); // TODO protect
+ // against infinite
+ // recursion.
+ }
+ default: {
+ throw new IOException("Method failed: "
+ + aMethod.getStatusLine());
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.dom4j.Element;
+
+/**
+ * An action defined on a page.
+ */
+public interface Action {
+
+ /**
+ * The name of the action.
+ * @return Action name.
+ */
+ String getName();
+
+ /**
+ * Executes the action.
+ * @return New page as a result of the action.
+ * @throws PageException In case of an error obtaining the page.
+ */
+ Page execute() throws PageException;
+
+ /**
+ * Gets a description of the action. THe element returned is the action element
+ * itself.
+ * @return Content as XML.
+ */
+ Element getContent();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Configuration which determines how a specific page must be retrieved and
+ * what transformation should be applied to it.
+ */
+public interface Configuration {
+
+ /**
+ * Gets the page request based on the URL.
+ * @param aUrl Url of the page to retrieve.
+ * @return Page request.
+ */
+ PageRequest getRequest(String aUrl);
+
+ /**
+ * Gets the page request based on the type of the page instead
+ * of on the URL.
+ * @param aType Type of page.
+ * @return Page request.
+ */
+ PageRequest getRequest(PageType aType);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.apache.commons.httpclient.NameValuePair;
+
+
+/**
+ * The object that actually obtains pages based on URL.
+ */
+public interface Crawler {
+
+ /**
+ * Gets the content for a specific page.
+ * @param aUrl Url of page.
+ * @param aParameters Paremeters to supply.
+ * @return Page to retrieve.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Page getPage(String aUrl, NameValuePair[] aParameters) throws PageException;
+
+ /**
+ * Gets the content for a specific page.
+ * @param aUrl Url of page.
+ * @param aParameters Parameters to supply.
+ * @param aType Type of page.
+ * @return Page.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Page getPage(String aUrl, NameValuePair[] aParameters, PageType aType) throws PageException;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.w3c.dom.Document;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Gets a page by issueing a get request.
+ */
+public class GetPageRequest extends AbstractPageRequest {
+
+ /**
+ * Constructs the request.
+ * @param aMaxTries Maximum number of retries.
+ * @param aMaxDelay Maximum delay before executing the request.
+ * @param aParams Request parameters to use.
+ * @param aHeaders Request headers to use.
+ * @param aXslt XSLT to use.
+ */
+ public GetPageRequest(int aMaxTries, int aMaxDelay, NameValuePair[] aParams,
+ NameValuePair[] aHeaders, String aXslt, XslTransformer aTransformer) {
+ super(aMaxTries, aMaxDelay, aParams, aHeaders, aXslt, aTransformer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#getPage(org.apache.commons.httpclient.HttpClient)
+ */
+ public Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient)
+ throws PageException {
+ HttpMethod method = new GetMethod(aUrl);
+ NameValuePair[] params = getParameters(aParams);
+ if (params.length > 0) {
+ String oldQueryString = method.getQueryString();
+ method.setQueryString(params);
+ String queryString = method.getQueryString();
+ if (oldQueryString.length() > 0) {
+ queryString = queryString + '&' + oldQueryString;
+ method.setQueryString(queryString);
+ }
+ }
+ try {
+ return executeMethod(aClient, method);
+ } catch (TransformerException e) {
+ throw new PageException("Transformation problem for url " + aUrl, e);
+ } catch (IOException e) {
+ throw new PageException("Problem getting " + aUrl, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.dom4j.Element;
+
+/**
+ * Represents a retrieved page.
+ */
+public interface Page {
+
+ /**
+ * Gets the content of the page as raw XML.
+ * @return Page content.
+ */
+ Element getContent();
+
+ /**
+ * Obtains the links available on the page.
+ * @return Link names.
+ */
+ Action[] getActions();
+
+ /**
+ * Gets the named action. Only works if the action name is unique.
+ * @param aName Name of the action.
+ * @return Action object.
+ */
+ Action getAction(String aName);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Exception thrown when there is a problem in retrieving or transforming the
+ * page.
+ */
+public class PageException extends Exception {
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public PageException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aCause Cause of the exception.
+ */
+ public PageException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.w3c.dom.Document;
+
+/**
+ * Represents a specific request to obtain and transform a page.
+ */
+public interface PageRequest {
+
+ /**
+ * Gets a page as an XML document.
+ * @param aUrl Url of the page.
+ * @param aParams Additional parameters to supply.
+ * @param aClient Http client to use.
+ * @return Client.
+ * @throws PageException In case of problems retrieving the page.
+ */
+ Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient) throws PageException;
+
+ /**
+ * Overrides the Xslt to use. This is used when the transformed page specifies
+ * the page type explicitly for an action.
+ * @param aXslt Xslt to use.
+ */
+ void overrideXslt(String aXslt);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+/**
+ * Represents the type of a page determining how the HTML should be transformed into
+ * XML.
+ */
+public class PageType {
+
+ /**
+ * Type string.
+ */
+ private String _type;
+
+ /**
+ * Constructs the type.
+ * @param aType Type.
+ */
+ public PageType(String aType) {
+ _type = aType;
+ }
+
+ /**
+ * Gets the type.
+ * @return Type.
+ */
+ public String getType() {
+ return _type;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "PageType(type='" + _type + "')";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PageType)) {
+ return false;
+ }
+ return toString().equals(obj.toString());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.w3c.dom.Document;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Retrieving pages using the post method.
+ */
+public class PostPageRequest extends AbstractPageRequest {
+
+ /**
+ * Constructs the request.
+ * @param aMaxTries Maximum number of retries.
+ * @param aMaxDelay Maximum delay before executing the request.
+ * @param aParams Request parameters to use.
+ * @param aHeaders Request headers to use.
+ * @param aXslt XSLT to use.
+ */
+ public PostPageRequest(int aMaxTries, int aMaxDelay,
+ NameValuePair[] aParams,
+ NameValuePair[] aHeaders,
+ String aXslt, XslTransformer aTransformer) {
+ super(aMaxTries, aMaxDelay, aParams, aHeaders, aXslt, aTransformer);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.PageRequest#execute(java.lang.String,
+ * org.apache.commons.httpclient.HttpClient)
+ */
+ public Document execute(String aUrl, NameValuePair[] aParams, HttpClient aClient)
+ throws PageException {
+ PostMethod method = new PostMethod(aUrl);
+ method.addParameters(getParameters(aParams));
+ try {
+ return executeMethod(aClient, method);
+ } catch (TransformerException e) {
+ throw new PageException("Transformation problem for url " + aUrl, e);
+ } catch (IOException e) {
+ throw new PageException("Problem getting page " + aUrl, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Action implementation.
+ */
+public class ActionImpl implements Action {
+
+ private Crawler _crawler;
+
+ private Element _content;
+
+ private String _name;
+
+ private String _reference;
+
+ private PageType _type;
+
+ private NameValuePair[] _parameters;
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCrawler
+ * Crawler to use.
+ * @param aContent
+ * Content of the action element in the page where the action
+ * occurs.
+ * @param aName
+ * Name of the action.
+ * @param aReference
+ * URL of the reference.
+ * @param aParameters Parameters to use for the action.
+ */
+ public ActionImpl(Crawler aCrawler, Element aContent, String aName,
+ String aReference, NameValuePair[] aParameters) {
+ _crawler = aCrawler;
+ _content = aContent;
+ _name = aName;
+ _reference = aReference;
+ _type = null;
+ _parameters = aParameters;
+ }
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCrawler
+ * Crawler to use.
+ * @param aContent
+ * Content of the action element in the page where the action
+ * occurs.
+ * @param aName
+ * Name of the action.
+ * @param aReference
+ * URL of the reference.
+ * @param aType
+ * Type of the referenced page.
+ * @param aParameters Parameters to use.
+ */
+ public ActionImpl(Crawler aCrawler, Element aContent, String aName,
+ String aReference, PageType aType, NameValuePair[] aParameters) {
+ _crawler = aCrawler;
+ _content = aContent;
+ _name = aName;
+ _reference = aReference;
+ _type = aType;
+ _parameters = aParameters;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#getName()
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#execute()
+ */
+ public Page execute() throws PageException {
+ if (_type == null) {
+ return _crawler.getPage(_reference, _parameters);
+ }
+ return _crawler.getPage(_reference, _parameters, _type);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Action#getContent()
+ */
+ public Element getContent() {
+ return _content;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if ( !(obj instanceof ActionImpl )) {
+ return false;
+ }
+ ActionImpl action = (ActionImpl)obj;
+ return _reference.equals(action._reference) &&
+ _type.equals(action._type);
+ }
+}
--- /dev/null
+package org.wamblee.crawler.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.xml.XslTransformer;
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.
+ */
+
+/**
+ * Test application which uses the crawler.
+ */
+public final class App {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private App() {
+ // Empty
+ }
+
+ /**
+ * Runs a test program.
+ *
+ * @param aArgs
+ * Arguments. First argument is the crawler config file name and
+ * second argument is the start url.
+ * @throws Exception
+ * In case of problems.
+ */
+ public static void main(String[] aArgs) throws Exception {
+ String configFileName = aArgs[0];
+ String starturl = aArgs[1];
+
+ ConfigurationParser parser = new ConfigurationParser(new XslTransformer());
+ InputStream configFile = new FileInputStream(new File(configFileName));
+ Configuration config = parser.parse(configFile);
+
+ HttpClient client = new HttpClient();
+ // client.getHostConfiguration().setProxy("localhost", 3128);
+
+ Crawler crawler = new CrawlerImpl(client, config);
+
+ System.out.println("Retrieving: " + starturl);
+ Page page = crawler.getPage(starturl, new NameValuePair[0]);
+ showPage(page);
+ page = page.getAction("channels-favorites").execute();
+ recordInterestingShows(page);
+ showPage(page);
+ page = page.getAction("Nederland 1").execute();
+ showPage(page);
+ page = page.getAction("right-now").execute();
+ showPage(page);
+ page = page.getAction("Het elfde uur").execute();
+ showPage(page);
+ }
+
+ /**
+ * @param starturl
+ * @param crawler
+ */
+ private static void showPage(Page aPage) {
+ Action[] links = aPage.getActions();
+ for (Action link : links) {
+ System.out.println("Link found '" + link.getName() + "'");
+ }
+ Element element = aPage.getContent();
+ System.out.println("Retrieved content: " + element.asXML());
+ }
+
+ private static void recordInterestingShows(Page page) throws PageException {
+ Action[] channels = page.getActions();
+ for (Action channel : channels) {
+ examineChannel(channel.getName(), channel.execute().getAction(
+ "right-now").execute());
+ }
+ }
+
+ private static void examineChannel(String aChannel, Page aPage)
+ throws PageException {
+ Action[] programs = aPage.getActions();
+ for (Action program : programs) {
+ System.out.println(aChannel + " - " + program.getName());
+ if (program.getName().toLowerCase().matches(".*babe.*")) {
+ Page programPage = program.execute();
+ Action record = programPage.getAction("record");
+ System.out.println("Recording possible: " + record != null);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.util.regex.Pattern;
+
+/**
+ * Configuration item for obtaining an object in case a pattern matches.
+ */
+class ConfigItem<ValueType> {
+
+ private Pattern _pattern;
+
+ private ValueType _value;
+
+ /**
+ * Constructs the item.
+ * @param aPattern Pattern.
+ * @param aValue Value.
+ */
+ protected ConfigItem(String aPattern, ValueType aValue) {
+ _pattern = Pattern.compile(aPattern);
+ _value = aValue;
+ }
+
+ /**
+ * Returns the object in case the value matches.
+ * @param aValue Value to match.
+ * @return Object in case there is a match, null otherwise.
+ */
+ protected ValueType match(String aValue) {
+ if (!_pattern.matcher(aValue).matches()) {
+ return null;
+ }
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.util.List;
+
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Implementation of the configuration for the crawler.
+ */
+public class ConfigurationImpl implements Configuration {
+
+ private List<UrlConfig> _urlConfig;
+
+ private List<PageTypeConfig> _pageTypeConfig;
+
+ /**
+ * Constructs the configuration.
+ * @param aUrlConfig List of URL configuration elements.
+ * @param aPageTypeConfig List of page type configuration elements.
+ */
+ public ConfigurationImpl(List<UrlConfig> aUrlConfig,
+ List<PageTypeConfig> aPageTypeConfig) {
+ _urlConfig = aUrlConfig;
+ _pageTypeConfig = aPageTypeConfig;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Configuration#getRequest(java.lang.String)
+ */
+ public PageRequest getRequest(String aUrl) {
+
+ for (UrlConfig config : _urlConfig) {
+ PageRequest request = config.getRequest(aUrl);
+ if (request != null) {
+ return request;
+ }
+ }
+ throw new RuntimeException("No configuration matched the URL '" + aUrl
+ + "'");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Configuration#getRequest(org.wamblee.crawler.PageType)
+ */
+ public PageRequest getRequest(PageType aType) {
+ for (PageTypeConfig config : _pageTypeConfig) {
+ PageRequest request = config.getRequest(aType.getType());
+ if (request != null) {
+ return request;
+ }
+ }
+ throw new RuntimeException("No configuration matched type '" + aType
+ + "'");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.GetPageRequest;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PostPageRequest;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * Parsing of the configuration from an XML file.
+ */
+public class ConfigurationParser {
+
+ private static final String ELEM_URL = "url";
+
+ private static final String ELEM_TYPE = "type";
+
+ private static final String ELEM_PATTERN = "pattern";
+
+ private static final String ELEM_METHOD = "method";
+
+ private static final String ELEM_XSLT = "xslt";
+
+ private static final String ELEM_PARAM = "param";
+
+ private static final String ELEM_HEADER = "header";
+
+ private static final String AT_NAME = "name";
+
+ private static final String AT_VALUE = "value";
+
+ private static final String METHOD_POST = "post";
+
+ private static final String METHOD_GET = "get";
+
+ private static final int MAX_TRIES = 3;
+
+ private static final int MAX_DELAY = 10000;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the configuration parser.
+ */
+ public ConfigurationParser(XslTransformer aTransformer) {
+ _transformer = aTransformer;
+ }
+
+ /**
+ * Parses the configuration from an input stream.
+ * @param aStream Input file.
+ * @return Configuration.
+ */
+ public Configuration parse(InputStream aStream) {
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(aStream);
+
+ Element root = document.getRootElement();
+ List<UrlConfig> urlConfigs = parseUrlConfigs(root);
+ List<PageTypeConfig> pageTypeConfigs = parsePageTypeConfigs(root);
+ return new ConfigurationImpl(urlConfigs, pageTypeConfigs);
+ } catch (DocumentException e) {
+ throw new RuntimeException("Problem parsing config file", e);
+ }
+ }
+
+ /**
+ * Parses the URL-based configuration.
+ * @param aRoot Root of the configuration file document.
+ * @return List of URL-based configurations.
+ */
+ private List<UrlConfig> parseUrlConfigs(Element aRoot) {
+ List<UrlConfig> configs = new ArrayList<UrlConfig>();
+ for (Iterator i = aRoot.elementIterator(ELEM_URL); i.hasNext();) {
+ Element url = (Element) i.next();
+ UrlConfig config = parseUrlConfig(url);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ /**
+ * Parses the page type based configurations.
+ * @param aRoot Root of the configuration file document.
+ * @return LIst of page type based configurations.
+ */
+ private List<PageTypeConfig> parsePageTypeConfigs(Element aRoot) {
+ List<PageTypeConfig> configs = new ArrayList<PageTypeConfig>();
+ for (Iterator i = aRoot.elementIterator(ELEM_TYPE); i.hasNext();) {
+ Element url = (Element) i.next();
+ PageTypeConfig config = parsePageTypeConfig(url);
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ /**
+ * Parses a URL-based configuration.
+ * @param aUrlElem Configuration element.
+ * @return Configuration.
+ */
+ private UrlConfig parseUrlConfig(Element aUrlElem) {
+ String pattern = aUrlElem.elementText(ELEM_PATTERN);
+ PageRequest request = parseRequestConfig(aUrlElem);
+ return new UrlConfig(pattern, request);
+ }
+
+ /**
+ * Parses a page type based configuration.
+ * @param aTypeElem Configuration element.
+ * @return Configuration.
+ */
+ private PageTypeConfig parsePageTypeConfig(Element aTypeElem) {
+ String pattern = aTypeElem.elementText(ELEM_PATTERN);
+ PageRequest request = parseRequestConfig(aTypeElem);
+ return new PageTypeConfig(pattern, request);
+ }
+
+ /**
+ * Parses a request configuration describing how to execute requests.
+ * @param aElem Configuration element.
+ * @return Page request.
+ */
+ private PageRequest parseRequestConfig(Element aElem) {
+ String method = aElem.elementText(ELEM_METHOD);
+ String xslt = aElem.elementText(ELEM_XSLT);
+ List<NameValuePair> params = parseNameValuePairs(aElem, ELEM_PARAM);
+ List<NameValuePair> headers = parseNameValuePairs(aElem, ELEM_HEADER);
+
+ NameValuePair[] paramsArray = params.toArray(new NameValuePair[0]);
+ NameValuePair[] headersArray = headers.toArray(new NameValuePair[0]);
+ PageRequest request;
+ if (METHOD_POST.equals(method)) {
+ request = new PostPageRequest(MAX_TRIES, MAX_DELAY, paramsArray, headersArray,
+ xslt, _transformer);
+ } else if (METHOD_GET.equals(method) || method == null) {
+ request = new GetPageRequest(MAX_TRIES, MAX_DELAY, paramsArray, headersArray,
+ xslt, _transformer);
+ } else {
+ throw new RuntimeException("Unknown request method '" + method
+ + "'. Only " + METHOD_GET + " and " + METHOD_POST
+ + " are supported");
+ }
+ return request;
+ }
+
+ /**
+ * @param aElem
+ * @return
+ */
+ private List<NameValuePair> parseNameValuePairs(Element aElem, String aElemName) {
+ List<NameValuePair> headers = new ArrayList<NameValuePair>();
+ for (Iterator i = aElem.elementIterator(aElemName); i.hasNext();) {
+ Element paramElem = (Element) i.next();
+ NameValuePair header = parseParameter(paramElem);
+ headers.add(header);
+ }
+ return headers;
+ }
+
+ /**
+ * Parses a parameter definition.
+ * @param aParam Parameter.
+ * @return Name value pair describing a parameter.
+ */
+ private NameValuePair parseParameter(Element aParam) {
+ String name = aParam.attributeValue(AT_NAME);
+ String value = aParam.attributeValue(AT_VALUE);
+ return new NameValuePair(name, value);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+import org.dom4j.io.DOMReader;
+import org.w3c.dom.Document;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.PageRequest;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Crawler implementation.
+ */
+public class CrawlerImpl implements Crawler {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerImpl.class);
+
+ private HttpClient _client;
+
+ private Configuration _config;
+
+ /**
+ * Constructs the crawler.
+ *
+ * @param aClient
+ * Http client to use.
+ * @param aConfig
+ * Configuration.
+ */
+ public CrawlerImpl(HttpClient aClient, Configuration aConfig) {
+ _client = aClient;
+ _config = aConfig;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Crawler#getPage(java.lang.String)
+ */
+ public Page getPage(String aUrl, NameValuePair[] aParams) throws PageException {
+ LOG.debug("Getting page: url = '" + aUrl + "'");
+ PageRequest request = _config.getRequest(aUrl);
+ Document content = request.execute(aUrl, aParams, _client);
+ return transformToDom4jDoc(aUrl, content);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Crawler#getPage(java.lang.String,
+ * java.lang.String)
+ */
+ public Page getPage(String aUrl, NameValuePair[] aParams, PageType aType) throws PageException {
+ LOG.debug("Getting page: url = '" + aUrl + "', type = '" + aType + "'");
+ PageRequest request = _config.getRequest(aType);
+ Document content = request.execute(aUrl, aParams, _client);
+ return transformToDom4jDoc(aUrl, content);
+ }
+
+ /**
+ * Converts a w3c DOM document to a page object.
+ * @param content DOM document.
+ * @return
+ */
+ private Page transformToDom4jDoc(String aUrl, Document content) {
+ DOMReader reader = new DOMReader();
+ org.dom4j.Document dom4jDoc = reader.read(content);
+ Element root = dom4jDoc.getRootElement();
+ dom4jDoc.remove(root);
+
+ return new PageImpl(aUrl, this, replaceReferencesWithContent(root));
+ }
+
+ /**
+ * Perform crawling. Find references in the retrieved content and replace
+ * them by the content they refer to by retrieving the appropriate pages as
+ * well.
+ *
+ * @param content
+ * Content which must be made complete.
+ * @return Fully processed content.
+ */
+ private Element replaceReferencesWithContent(Element content) {
+ return content; // TODO implement.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.XPath;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageType;
+
+/**
+ * Page implementation.
+ */
+public class PageImpl implements Page {
+
+ private static final String ELEM_NAME = "action";
+
+ private static final String ATT_NAME = "name";
+
+ private static final String ATT_HREF = "reference";
+
+ private static final String ATT_TYPE = "type";
+
+ private static final String ELEM_PARAM = "param";
+
+ private static final String ATT_VALUE = "value";
+
+ private String _href;
+
+ private Crawler _crawler;
+
+ private Element _content;
+
+ private Action[] _actions;
+
+ /**
+ * Constructs a page.
+ *
+ * @param aContent
+ */
+ public PageImpl(String aHref, Crawler aCrawler, Element aContent) {
+ _href = aHref;
+ _crawler = aCrawler;
+ _content = aContent;
+ _actions = computeActions();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getLinkNames()
+ */
+ private Action[] computeActions() {
+ XPath xpath = DocumentHelper.createXPath(ELEM_NAME);
+ List<Element> results = (List<Element>) xpath.selectNodes(_content);
+ List<Action> names = new ArrayList<Action>();
+ for (Element elem : results) {
+ String name = elem.attributeValue(ATT_NAME);
+ String href = elem.attributeValue(ATT_HREF);
+ String type = elem.attributeValue(ATT_TYPE);
+ NameValuePair[] params = getMandatoryParameters(elem);
+ href = absolutizeHref(_href, href);
+ if (type == null) {
+ names.add(new ActionImpl(_crawler, elem, name, href, params));
+ } else {
+ names.add(new ActionImpl(_crawler, elem, name, href,
+ new PageType(type), params));
+ }
+ }
+ return names.toArray(new Action[0]);
+ }
+
+ /**
+ * Absolutize the hyperlink
+ * @param aPageHref Absolute page reference.
+ * @param aLinkHref Possibly relative link reference.
+ * @return Absolute hyperlink.
+ */
+ private String absolutizeHref(String aPageHref, String aLinkHref) {
+
+ try {
+ URL pageUrl = new URL(aPageHref);
+ URL newUrl = new URL(pageUrl, aLinkHref);
+ return newUrl.toString(); // TODO need to use URL instead of String throughout the code.
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Malformed URL", e);
+ }
+ }
+
+ private NameValuePair[] getMandatoryParameters(Element aAction) {
+ List<NameValuePair> result = new ArrayList<NameValuePair>();
+ for (Element param: (List<Element>)aAction.elements(ELEM_PARAM)) {
+ String name = param.attributeValue(ATT_NAME);
+ String value = param.attributeValue(ATT_VALUE);
+ result.add(new NameValuePair(name, value));
+ }
+ return result.toArray(new NameValuePair[0]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getContent()
+ */
+ public Element getContent() {
+ return _content;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getActions()
+ */
+ public Action[] getActions() {
+ return _actions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.Page#getAction(java.lang.String)
+ */
+ public Action getAction(String aName) {
+ List<Action> results = new ArrayList<Action>();
+ for (Action action : _actions) {
+ if (action.getName().equals(aName)) {
+ results.add(action);
+ }
+ }
+ if (results.size() == 0) {
+ return null;
+ }
+ if (results.size() > 1) {
+ throw new RuntimeException("Duplicate action '" + aName + "'");
+ }
+ return results.get(0);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.wamblee.crawler.PageRequest;
+
+/**
+ * Page type configuration.
+ */
+public class PageTypeConfig extends ConfigItem<PageRequest> {
+
+ /**
+ * Constructs the configuration.
+ * @param aPattern Page type pattern.
+ * @param aRequest Page request.
+ */
+ public PageTypeConfig(String aPattern, PageRequest aRequest) {
+ super(aPattern, aRequest);
+ }
+
+ /**
+ * Returns the request in case the type matches.
+ * @param aType Page type.
+ * @return Request if the type matches, null otherwise.
+ */
+ public PageRequest getRequest(String aType) {
+ return match(aType);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.impl;
+
+import org.wamblee.crawler.PageRequest;
+
+/**
+ * Represents the configuration for specific URLs.
+ */
+public class UrlConfig extends ConfigItem<PageRequest> {
+ /**
+ * Constructs the information for how to perform a request for a specific
+ * URL.
+ *
+ * @param aPattern
+ * Pattern that the URL must match.
+ * @param aRequest
+ * Request that must be executed to retrieve the URL.
+ */
+ public UrlConfig(String aPattern, PageRequest aRequest) {
+ super(aPattern, aRequest);
+ }
+
+ /**
+ * Gets the request to execute.
+ *
+ * @return Request, or null if the URL does not match.
+ */
+ public PageRequest getRequest(String aUrl) {
+ return match(aUrl);
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides the implementations of the web crawler interfaces.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides the basic interfaces for a web crawler.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+This is a crawler for the KiSS Electronic Program Guide that can be used for instance with the KiSS DP558 hard-disc recorder. It uses the basic crawler for its implementation.
+
+Based on preferences for recording programs, the crawler automatically records programs that are scheduled to run on the same day. This saves a lot of manual work in recording programs.
+
+The final idea is to define ones own interests in television programs and have the crawler record them automatically or send notifications of possibly interesting programs. Whether programs should be recorded can be determined by several criteria such as program title, channel, time of day, and keywords in the description.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<crawler>
+
+ <type>
+ <pattern>login</pattern>
+ <method>post</method>
+ <xslt>login-graphic.xsl</xslt>
+ <param name="user" value="youruserid"/>
+ <param name="passw1d" value="yourpassword"/>
+ <param name="GMode" value="GraphicMode"/>
+ <param name="SavePlayerID" value="1"/>
+ <param name="submit" value="Login"/>
+ <header name="Referer" value="http://epg.kml.kiss-technology.com/login.php"/>
+ </type>
+
+ <type>
+ <pattern>channels-favorites</pattern>
+ <xslt>channels-favorites-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>channel-overview</pattern>
+ <xslt>channel-overview.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>right-now</pattern>
+ <xslt>channel-right-now-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>tomorrow</pattern>
+ <xslt>channel-right-now-graphic.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>program-info</pattern>
+ <xslt>program-info-mobile.xsl</xslt>
+ </type>
+
+ <type>
+ <pattern>recorded</pattern>
+ <xslt>recorded.xsl</xslt>
+ </type>
+
+ <url>
+ <pattern>http://epg.kml.kiss-technology.com/login.php</pattern>
+ <xslt>mainpage.xsl</xslt>
+ </url>
+
+ <url>
+ <pattern>.*</pattern>
+ <method>get</method>
+ <xslt>identity.xsl</xslt>
+ </url>
+</crawler>
+
--- /dev/null
+
+
+############################################################################
+# Mail server configuration
+############################################################################
+org.wamblee.crawler.smtp.host=falcon
+org.wamblee.crawler.smtp.port=25
+org.wamblee.crawler.smtp.username=
+org.wamblee.crawler.smtp.password=
+
+############################################################################
+# Mail notification configuration
+############################################################################
+org.wamblee.crawler.notification.from=kiss@wamblee.org
+org.wamblee.crawler.notification.to=erik@bladibla.org
+org.wamblee.crawler.notification.subject=Recording summary for tomorrow
+
--- /dev/null
+<programs>
+
+ <program>
+ <category>horror</category>
+ <action>notify</action>
+ <match field="description">horror</match>
+ </program>
+
+ <program>
+ <category>horror</category>
+ <action>notify</action>
+ <match>the.*ghost.*whisperer</match>
+ </program>
+
+ <program>
+ <category>films</category>
+ <action>notify</action>
+ <match field="keywords">film</match>
+ <match field="description">horror|actie|thriller</match>
+ </program>
+
+ <program>
+ <category>wetenschap</category>
+ <action>notify</action>
+ <match field="description">wetenschap</match>
+ </program>
+
+ <program>
+ <category>science fiction</category>
+ <action>notify</action>
+ <match field="description">sf-|(sci-fi)|(science fiction)</match>
+ </program>
+
+ <program>
+ <match>invasion</match>
+ </program>
+
+ <program>
+ <action>notify</action>
+ <category>documentaires</category>
+ <match>(zembla)|(uur.*wolf)|(andere tijden)|(de.*leugen.*regeert)</match>
+ </program>
+
+ <program>
+ <priority>20</priority>
+ <match>star.*gate</match>
+ </program>
+
+ <program>
+ <match>six.*feet.*under</match>
+ </program>
+
+ <program>
+ <priority>11</priority>
+ <match>battlestar</match>
+ </program>
+
+ <program>
+ <priority>10</priority>
+ <match>star trek</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>((dr)|(doct.*)).*who</match>
+ </program>
+
+ <program>
+ <priority>8</priority>
+ <match>little britain</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>the.*x.*files</match>
+ </program>
+
+ <program>
+ <priority>9</priority>
+ <match>buffy.*vampire.*slayer</match>
+ </program>
+
+ <program>
+ <action>notify</action>
+ <match>de.*wereld.*draait.*door</match>
+ </program>
+
+ <program>
+ <priority>8</priority>
+ <match>jag</match>
+ </program>
+
+ <program>
+ <priority>5</priority>
+ <match>shouf shouf</match>
+ </program>
+
+ <program>
+ <match>red dwarf</match>
+ </program>
+
+ <program>
+ <match>top gear</match>
+ </program>
+
+ <program>
+ <match>bedreigde.*paradijzen</match>
+ </program>
+
+ <program>
+ <match>wie is de baas</match>
+ </program>
+
+ <program>
+ <category>wetenschap</category>
+ <action>notify</action>
+ <match>brainiac</match>
+ </program>
+
+ <program>
+ <category>auto</category>
+ <match>wegmisbruikers|(blik.*op.*weg)</match>
+ </program>
+
+</programs>
--- /dev/null
+\r
+\r
+\r
+cd ../conf\r
+\r
+\r
+set CP=.;../lib/wamblee-support-0.2-SNAPSHOT.jar;../lib/wamblee-crawler-kiss-0.2-SNAPSHOT.jar\r
+\r
+\r
+java -classpath %CP% org.wamblee.crawler.kiss.main.KissCrawlerBootstrapper ../lib config.xml programs.xml\r
+\r
--- /dev/null
+#!/bin/ksh
+
+cd $( dirname $0 )/../conf
+
+CP=.:../lib/wamblee-support-0.2-SNAPSHOT.jar:../lib/wamblee-crawler-kiss-0.2-SNAPSHOT.jar
+
+set -x
+java -classpath $CP org.wamblee.crawler.kiss.main.KissCrawlerBootstrapper \
+ ../lib config.xml programs.xml
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<title></title>
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Nederland 1 *</title>
+</head>
+<body bgcolor="#ffffff">
+d> <img src="../images/KiSS_Logo_small.gif"
+align="center" />
+<h1> Nederland 1 *</h1>
+
+<h2>What's on?</h2>
+
+<a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=0&now=1&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Right now</a> - 12:11, Monday 13th March<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=20&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)1~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Evening</a> - Starting 20:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=16&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)2~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Afternoon</a> - Starting 16:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=12&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)3~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Noon</a> - Starting 12:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=6&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)4~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Morning</a> - Starting 6:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=36&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Tomorrow</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=60&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)8~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Wednesday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=84&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)9~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Thursday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=108&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)10~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Friday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=132&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)11~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Saturday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&view=156&tz=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)12~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Sunday</a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&page=0&station=0&tz=1&sel=0&back=$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:11, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<meta name="generator" content="Adobe GoLive" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<script language="JavaScript1.2" src="../popup15.js"
+type="text/javascript">
+</script>
+
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<div id="popuptitle"
+style="visibility:hidden;position:absolute;z-index:1000;top:-100">
+</div>
+
+<script language="JavaScript1.2" type="text/javascript">
+ Style[0]=["#ffffff","#6f7e8f","","","font-family:arial,helvetica,geneva,swiss,sunsans-regular",2,"#183457","#d0dce8","","","font-family:arial,helvetica,geneva,swiss,sunsans-regular",1,,,2,"#92AAC6",4,,,,,"",,,,]
+ var TipId="popuptitle"
+ var FiltersEnabled = 1
+ mig_clay()
+
+</script>
+
+<h1><img src="../images/KiSS_Logo.gif" align="right" /><a
+href="tvstart.php"><img border="0"
+src="../images/tvguide_logo.gif" /></a></h1>
+
+<br />
+
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td align="left">
+<h1><img
+src="http://epg.kml.kiss-technology.com/tv/logos/6_w60.gif"
+align="middle" hspace="6" /> Nederland 1</h1>
+
+<p></p>
+</td>
+</tr>
+</table>
+
+<table align="center" style="width: 400px; text-align: left;"
+border="0" cellpadding="0" cellspacing="0">
+<tbody>
+<tr>
+<td style="vertical-align: bottom; width: 400px;"><img
+src="../images/tabs_none.gif" border="0" alt=""
+usemap="#tabs_Map" /><map id="tabs_Map" name="tabs_Map">
+<area shape="poly" alt="What's On Now?"
+coords="126,30, 118,0, 0,0, 0,30"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?reload=1&mode=3&station=0&view=0&now=1"
+ class="grid" />
+<area shape="poly" alt="Favorite Shows"
+coords="239,0, 247,30, 128,30, 120,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+<area shape="poly" alt="Movies"
+coords="241,0, 250,30, 309,30, 300,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+<area shape="poly" alt="Sports"
+coords="302,0, 311,30, 370,30, 362,0"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0"
+ class="grid" />
+</map> </td>
+</tr>
+</tbody>
+</table>
+
+<table class="tvlist" align="center" width="400" border="0"
+cellspacing="0" cellpadding="1">
+<tr height="25">
+<td align="left" bgcolor="black" colspan="2"><font
+color="white"><b> Wednesday 23rd </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 23:15 -
+00:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[0]=["Karels keuze","23:15 - 00:00<br>Documentaire Het schokkende verhaal van een Turkse familie uit Denemarken, die verscheurd raakt door eerwraak. Een minutieuze reconstructie van een hartverscheurend drama, verteld door het meisje om wie de ruzie begon en haar broer, die door haar vader gedwongen werd de trekker over te halen. Hij was toen dertien jaar en moest meedoen omdat hij te jong was om veroordeeld te kunne [...]"]
+</script>
+
+ <a onmouseover="stm(Text[0],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787582&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Karels keuze</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:00 -
+00:30 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[1]=["Nederland helpt","00:00 - 00:30<br>Serie reportages over Nederlanders, bekend of onbekend, die zich inzetten voor een goed doel."]
+</script>
+
+ <a onmouseover="stm(Text[1],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787583&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland helpt</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 00:30 -
+00:55 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[2]=["Man beet hond","00:30 - 00:55"]
+</script>
+
+ <a onmouseover="stm(Text[2],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787584&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:55 -
+06:45 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[3]=["Nacht-tv: Netwerk herhaling","00:55 - 06:45"]
+</script>
+
+ <a onmouseover="stm(Text[3],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787585&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 06:45 -
+07:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[4]=["Nederland in beweging","06:45 - 07:00<br>Gymnastiek met als doel Nederlanders in beweging te krijgen. Met voedingstips, de favoriete sport van een bekende Nederlander en sportieve uitgaanstips."]
+</script>
+
+ <a onmouseover="stm(Text[4],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849473&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 07:00 -
+09:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[5]=["NOS-Journaal","07:00 - 09:00<br>Gevolgd door herhalingen."]
+</script>
+
+ <a onmouseover="stm(Text[5],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849474&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:00 -
+09:10 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[6]=["NOS-Journaal","09:00 - 09:10"]
+</script>
+
+ <a onmouseover="stm(Text[6],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849475&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:10 -
+09:30 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[7]=["Nederland in beweging","09:10 - 09:30<br>Gymnastiek met als doel Nederlanders in beweging te krijgen. Met voedingstips, de favoriete sport van een bekende Nederlander en sportieve uitgaanstips."]
+</script>
+
+ <a onmouseover="stm(Text[7],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849476&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:30 -
+09:55 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[8]=["That's the question","09:30 - 09:55<br>Quiz waarin twee deelnemers zo snel mogelijk een vraag moeten raden. Aan de hand van het beantwoorden van vragen worden er letters van de vraag zichtbaar. Met het goed beantwoorden van de vragen verdient de kandidaat seconden waarmee hij/zij later de finale speelt."]
+</script>
+
+ <a onmouseover="stm(Text[8],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849477&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:55 -
+11:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[9]=["TROS Zomeravondcafé","09:55 - 11:00<br>Live programma vanaf het binnenplein van het TROS-gebouw. Iedere dag staat in het teken van een ander zomers thema. Bert Kuizenga praat met een bekende hoofdgast, die aansluit bij het thema van de dag. De actualiteit wordt behandeld door Angela Esajas. Verder twee muzikale acts en de kookrubriek met Ad Janssen. Bovendien is er twee keer per week een optreden van psycho-illusionis [...]"]
+</script>
+
+ <a onmouseover="stm(Text[9],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849478&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>TROS Zomeravondcafé</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 11:00 -
+11:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[10]=["Andere tijden","11:00 - 11:30<br>Geschiedenismagazine Bij het hardrijden op de schaats deed Nederland tijdens de Olympische Winterspelen weer op veel fronten mee, maar bij het kunstrijden hadden we niets meer te zoeken. Dat is wel eens anders geweest; in 1964 won Sjoukje Dijkstra in Innsbrück een gouden medaille. Over haar en haar hartsvriendin èn rivale Joan Haanappel gaat deze aflevering."]
+</script>
+
+ <a onmouseover="stm(Text[10],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849479&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Andere tijden</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 11:30 -
+12:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[11]=["De vloer op","11:30 - 12:00<br>Acteurs improviseren in het Bimhuis. -Vervangster gezocht. Met Carly Wijs, Hadewych Minis en Gijs Scholten van Aschat. Carly heeft nog ongeveer twee maanden te leven. Ze gaat naar de secretaresse van haar man en vraagt haar of zij na haar dood haar plaats wil innemen. In alle opzichten. -Burka zoekt imam. Met Leopold Witte en Pierre Bokma. Pierre is burgemeester van een kleine Li [...]"]
+</script>
+
+ <a onmouseover="stm(Text[11],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849480&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De vloer op</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:00 -
+12:10 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[12]=["NOS-Journaal","12:00 - 12:10"]
+</script>
+
+ <a onmouseover="stm(Text[12],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849481&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 12:10 -
+12:40 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[13]=["Man beet hond","12:10 - 12:40<br>Het beste van het afgelopen jaar."]
+</script>
+
+ <a onmouseover="stm(Text[13],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849482&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:40 -
+13:00 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[14]=["Lingo","12:40 - 13:00<br>Woordspel waarin spelers zesletterwoorden moeten raden en deelnemers aan de Postcode Loterij met de PostcodeLingo-kaart mee kunnen spelen en waarin de bekendmaking van de dagpostcodes voor de Lingospelers thuis plaatsvindt."]
+</script>
+
+ <a onmouseover="stm(Text[14],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849483&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Lingo</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:00 -
+13:10 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[15]=["NOS-Journaal","13:00 - 13:10"]
+</script>
+
+ <a onmouseover="stm(Text[15],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849484&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 13:10 -
+13:15 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[16]=["NOS-Sportjournaal","13:10 - 13:15"]
+</script>
+
+ <a onmouseover="stm(Text[16],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849485&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Sportjournaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:15 -
+14:05 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[17]=["Warzone: Congo","13:15 - 14:05<br>Documentaire waarin het leven van jongeren in conflictgebieden op een indringende manier in beeld wordt gebracht. In Congo worden Gideon van Aartsen en zijn 25-jarige reisgenote Nynke Douma geconfronteerd met tienermoeders en heksenkinderen. In dit land vinden veel verkrachtingen plaats, waardoor de grote hoeveelheid tienermoeders kan worden verklaart. Ook onderzoeken ze het feno [...]"]
+</script>
+
+ <a onmouseover="stm(Text[17],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849486&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Warzone: Congo</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 14:05 -
+14:35 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[18]=["Wroeten","14:05 - 14:35<br>12-delige Tuinprogramma waarin Arjan Ederveen bevlogen verslag uitbrengt van wat er gebeurt in de tuin bij zijn boerderij: er groeien gewone en ongewone groenten, er bloeien bloemen en planten en elke week wordt er een logeerdier gebracht door een boer die wel eens met vakantie wil. Arjan wordt bijgestaan door Heilien Tonckens, expert in ecologisch tuinieren, en tuinman Hans Enge [...]"]
+</script>
+
+ <a onmouseover="stm(Text[18],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849487&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wroeten</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 14:35 -
+15:14 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[19]=["Spraakmakende zaken","14:35 - 15:14<br>8-delige Serie gesprekken over actuele kwesties. Paul Rosenmöller ontvangt zijn gasten in Paviljoen Het Oosten in Amsterdam, waar hij met hen praat over brandende kwesties uit het recente verleden. Het is behoorlijk bijzonder dat SGP-leider Bas van der Vlies tekst en uitleg komt geven over de vrouwenkwestie in de SGP. SGP-ers zijn uit overtuiging immers niet zo dol op telev [...]"]
+</script>
+
+ <a onmouseover="stm(Text[19],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849488&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Spraakmakende zaken</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 15:14 -
+15:16 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[20]=["Wilde Ganzen","15:14 - 15:16"]
+</script>
+
+ <a onmouseover="stm(Text[20],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849489&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wilde Ganzen</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 15:16 -
+16:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[21]=["Opsporing verzocht","15:16 - 16:00<br>Opsporingsprogramma i.s.m. politie en justitie voor een veiliger samenleving."]
+</script>
+
+ <a onmouseover="stm(Text[21],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849490&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Opsporing verzocht</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 16:00 -
+16:05 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[22]=["NOS-Journaal","16:00 - 16:05"]
+</script>
+
+ <a onmouseover="stm(Text[22],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849491&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 16:05 -
+17:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[23]=["Denkend aan Showroom","16:05 - 17:00<br>Herinneringen aan de kleurrijke mensen uit het spraakmakende programma Showroom (1977-1983)."]
+</script>
+
+ <a onmouseover="stm(Text[23],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849492&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Denkend aan Showroom</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:00 -
+17:05 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[24]=["NOS-Journaal","17:00 - 17:05"]
+</script>
+
+ <a onmouseover="stm(Text[24],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849493&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 17:05 -
+17:35 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[25]=["God voor...","17:05 - 17:35<br>Snel interviewprogramma over langzame zaken. Surinamers over feesten en God."]
+</script>
+
+ <a onmouseover="stm(Text[25],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849494&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>God voor...</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:35 -
+18:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[26]=["Himalaya with Michael Palin","17:35 - 18:30<br>6-delige Serie reportages Nadat Michael Palin eerder de uitdagingen van zeeën, polen en woestijnen is aangegaan, vormen de hoogste bergen ter wereld voor hem een natuurlijk doel. Hij volgt de Himalaya over de hele lengte van het gebergte, van de grens tussen Pakistan en Afghanistan door India, Nepal en Tibet en Yunnan in China. Waarna hij opnieuw de bergen oversteekt, naar A [...]"]
+</script>
+
+ <a onmouseover="stm(Text[26],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849495&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Himalaya with Michael Palin</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 18:30 -
+19:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[27]=["That's the question","18:30 - 19:00<br>Quiz waarin twee deelnemers zo snel mogelijk een vraag moeten raden. Aan de hand van het beantwoorden van vragen worden er letters van de vraag zichtbaar. Met het goed beantwoorden van de vragen verdient de kandidaat seconden waarmee hij/zij later de finale speelt."]
+</script>
+
+ <a onmouseover="stm(Text[27],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849496&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 19:00 -
+19:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[28]=["Man beet hond","19:00 - 19:30<br>Het beste van het afgelopen jaar."]
+</script>
+
+ <a onmouseover="stm(Text[28],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849497&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 19:30 -
+20:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[29]=["De confrontatie","19:30 - 20:00<br>Edwin is een goeduitziende jongen van twintig jaar. Hij heeft een aangeboren botafwijking en mist daardoor zijn bovenbenen. Hij kan kleine stukjes lopen, maar is in principe rolstoelgebonden. Vanaf zijn dertiende is Edwin rolstoelbasketbal gaan spelen. Eerst nog op lokaal niveau, maar na een inschrijving voor selectietrainingen in 2005 werd hij gescout voor het Nederlandse nationa [...]"]
+</script>
+
+ <a onmouseover="stm(Text[29],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849498&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De confrontatie</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 20:00 -
+20:30 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[30]=["NOS-Journaal","20:00 - 20:30"]
+</script>
+
+ <a onmouseover="stm(Text[30],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849499&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell1">
+<div align="center"><font color="#000000"> 20:30 -
+21:00 </font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[31]=["Netwerk","20:30 - 21:00<br>Actualiteiten van EO, KRO en NCRV."]
+</script>
+
+ <a onmouseover="stm(Text[31],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849500&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Netwerk</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" class="listCell2">
+<div align="center"><font color="#000000"> 21:00 -
+22:35 </font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[32]=["KRO Detectives: Judge John Deed","21:00 - 22:35<br>Misdaadserie Een jonge vrouw wordt bruut doodgeslagen. Gary Patterson bekent de moord. Hij heeft echter de mentale leeftijd van een jonge tiener en als hij zijn bekentenis ook nog intrekt, wordt het moeilijk de waarheid te achterhalen. Intussen onderzoekt Deed ook een zaak van grootschalige hypotheekfraude. Hierbij blijken procureurs betrokken. Met zijn hardnekkige onderzoek lijk [...]"]
+</script>
+
+ <a onmouseover="stm(Text[32],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849501&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>KRO Detectives: Judge John Deed</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#106F10">
+<div align="center"><font color="#FFFFFF"><b> 22:35 -
+23:05 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[33]=["Dochters + moeders","22:35 - 23:05<br>Ze heeft je gebaard, ze is lief, af en toe wil je haar het liefst achter het behang plakken maar het blijft toch je moeder. Ze is eigenwijs, komt altijd met de foute vriendjes thuis en luistert nooit maar het blijft je dochter. Is er een uniekere band dan die tussen dochter en moeder?."]
+</script>
+
+ <a onmouseover="stm(Text[33],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849502&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Dochters + moeders</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:05 -
+23:32 </b></font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[34]=["De God van Nederland","23:05 - 23:32<br>8-delige Serie reportages waarin de barometerstand van de wonderbaarlijke terugkeer van de christelijke God in de Hollandse polder gepeild wordt en die op zoek gaat naar het Nieuw Religieus Peil. Een serie over verlangen naar idealen, geborgenheid en gemeenschap en een zinvol bestaan als rituele dagsluiting voor gelovigen en ongelovigen, zoekers en zieners, dolende zielen en vast [...]"]
+</script>
+
+ <a onmouseover="stm(Text[34],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849503&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>De God van Nederland</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:32 -
+23:40 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[35]=["Wilde Ganzen","23:32 - 23:40"]
+</script>
+
+ <a onmouseover="stm(Text[35],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849504&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wilde Ganzen</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:40 -
+00:05 </b></font></div>
+</td>
+<td align="center" class="listCell2" width="10"></td>
+<td align="left" class="listCell2">
+<script type="text/javascript" language="JavaScript1.2">
+Text[36]=["Man beet hond","23:40 - 00:05"]
+</script>
+
+ <a onmouseover="stm(Text[36],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849505&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td width="100" align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 00:05 -
+06:45 </b></font></div>
+</td>
+<td align="center" class="listCell1" width="10"></td>
+<td align="left" class="listCell1">
+<script type="text/javascript" language="JavaScript1.2">
+Text[37]=["Nacht-tv: Netwerk herhaling","00:05 - 06:45"]
+</script>
+
+ <a onmouseover="stm(Text[37],Style[0])" onmouseout="htm()"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777849506&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></td>
+</tr>
+</table>
+
+<br />
+
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td rowspan="3"> </td>
+<td style="width: 140px; vertical-align: bottom;"><img
+src="../images/legend_green.gif" /> Currently
+showing<br />
+</td>
+<td style="width: 140px; vertical-align: bottom;"><img
+src="../images/legend_orange.gif" /> Favorite<br />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: middle;"><img
+src="../images/legend_blue.gif" /> Future showing<br />
+</td>
+<td style="vertical-align: middle;"><img
+src="../images/legend_red.gif" /> Scheduled
+recording<br />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: middle;"><img
+src="../images/legend_purple.gif" /> Movie<br />
+</td>
+<td style="vertical-align: middle;"> </td>
+</tr>
+</table>
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=2&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/back.gif" alt="Back" border="0"
+align="middle" /></a><a href="tvstart.php"><img
+src="../images/home.gif" alt="Home" border="0"
+align="middle" /></a><a href="../logout.php"><img
+src="../images/logout.gif" alt="Logout" border="0"
+align="middle" /></a> 22:38, Wednesday 23rd August</td>
+</tr>
+</table>
+</body>
+</html>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name="" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> Monday 21st </time></action>
+
+ <action name="NCRV Dokument: Het recht om te sterven" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787547&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 23:00 -23:45 </time></action>
+
+ <action name="Apocalyps Vietnam" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787548&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 23:45 -00:40 </time></action>
+
+ <action name="Man beet hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787549&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 00:40 -01:10 </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787550&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> 01:10 -06:45 </time></action>
+
+ <action name="" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"><time> Monday 21st </time></action>
+
+ <action name="" type="program-info" reference=""><time> 23:13, Monday 21st August</time></action>
+
+ </channel-right-now>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<meta name="generator" content="Adobe GoLive" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="right" />
+
+<h1>Nederland 1</h1>
+
+<table class="tvlist" align="center" width="100%" border="0"
+cellspacing="0" cellpadding="1">
+<tr height="25">
+<td align="left" bgcolor="black"><font
+color="white"><b> Monday 21st </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright_small.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 22:30 -
+00:15 </font></div>
+</td>
+<td align="left" class="listCell2"><a class="movie"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777728676&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>KRO Filmtheater: Hollywood ending</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 00:15 -
+06:45 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777728677&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Tekst tv</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 06:45 -
+07:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787518&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 07:00 -
+09:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787519&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:00 -
+09:10 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787520&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 09:10 -
+09:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787521&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nederland in beweging</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 09:30 -
+10:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787522&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 10:00 -
+11:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787523&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>TROS Muziekfeest in de ArenA 2004</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 11:00 -
+11:25 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787524&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Hello Goodbye</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 11:25 -
+12:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787525&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Wat Zou JIJ Doen?</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:00 -
+12:10 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787526&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 12:10 -
+12:40 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787527&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Taxi</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 12:40 -
+13:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787528&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Lingo</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:00 -
+13:10 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787529&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 13:10 -
+13:20 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787530&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Sportjournaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 13:20 -
+14:05 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787531&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Warzone: Zuid-Afrika</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 14:05 -
+15:20 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787532&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Tien maal Mozart</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 15:20 -
+16:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787533&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Kruispunt</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 16:00 -
+16:05 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787534&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 16:05 -
+17:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787535&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Oppassen & wegwezen</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:00 -
+17:05 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787536&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 17:05 -
+17:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787537&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Schepper & co</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 17:30 -
+18:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787538&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Himalaya with Michael Palin</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 18:30 -
+18:55 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787539&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>That's the question</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 18:55 -
+19:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787540&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 19:30 -
+20:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787541&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Ingang Oost</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 20:00 -
+20:30 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787542&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NOS-Journaal</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 20:30 -
+21:00 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787543&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Netwerk</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 21:00 -
+22:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787544&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Memories Tour d'Amour</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell1">
+<div align="center"><font color="#000000"> 22:00 -
+22:30 </font></div>
+</td>
+<td align="left" class="listCell1"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787545&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Hello Goodbye</b></a></td>
+</tr>
+
+<tr>
+<td align="center" class="listCell2">
+<div align="center"><font color="#000000"> 22:30 -
+23:00 </font></div>
+</td>
+<td align="left" class="listCell2"><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787546&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Villa historica</b></a></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#106F10">
+<div align="center"><font color="#FFFFFF"><b> 23:00 -
+23:45 </b></font></div>
+</td>
+<td align="left" bgcolor="#106F10"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787547&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>NCRV Dokument: Het recht om te sterven</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 23:45 -
+00:40 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787548&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Apocalyps Vietnam</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 00:40 -
+01:10 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787549&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Man beet hond</b></a></b></font></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="#1B4198">
+<div align="center"><font color="#FFFFFF"><b> 01:10 -
+06:45 </b></font></div>
+</td>
+<td align="left" bgcolor="#1B4198"><font
+color="#FFFFFF"><b> <a class="white"
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1777787550&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<b>Nacht-tv: Netwerk herhaling</b></a></b></font></td>
+</tr>
+
+<tr height="25">
+<td align="left" bgcolor="black"><font
+color="white"><b> Monday 21st </b></font></td>
+<td align="right" bgcolor="black"><font color="white"><b>Page
+1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=2&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/arrowright_small.gif" alt="Next" border="0"
+align="texttop" /></a> </font></td>
+</tr>
+</table>
+
+<table align="center" width="100%" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><br />
+<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=2&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+<img src="../images/back_small.gif" alt="Back" border="0"
+align="center" /></a> <a href="tvstart.php"><img
+src="../images/home_small.gif" alt="Home" border="0"
+align="center" /></a> <a href="../logout.php"><img
+src="../images/logout_small.gif" alt="Logout" border="0"
+align="center" /></a> 23:13, Monday 21st August</td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name=">>" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time> </time></action>
+
+ <action name="Wintertijd" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 23:55 - 00:40 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhalingen" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+00:50 - 06:15 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:45 - 06:59 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:59 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 07:00 - 07:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:10 - 07:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:30 - 07:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:40 - 08:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:00 - 08:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:10 - 08:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 08:30 - 08:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:40 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:00 - 09:10 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:10 - 09:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:30 - 09:55 - </time></action>
+
+ <action name="Schoondochter gezocht" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:55 - 10:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 10:50 - 11:15 - </time></action>
+
+ <action name="Appeltje voor de dorst" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+11:15 - 12:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:00 - 12:10 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:10 - 12:35 - </time></action>
+
+ <action name="Voor alle fans: Drukwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:35 - 12:57 - </time></action>
+
+ <action name="Trekking Lingo" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:57 - 13:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:00 - 13:10 - </time></action>
+
+ <action name="NOS-Sportjournaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:10 - 13:20 - </time></action>
+
+ <action name="Buitenhof" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:20 - 14:15 - </time></action>
+
+ <action name="Hoge bomen in de misdaad" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:15 - 14:55 - </time></action>
+
+ <action name="AVRO Dierenpark" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:55 - 15:20 - </time></action>
+
+ <action name="Kruispunt" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>15:20 - 16:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:00 - 16:05 - </time></action>
+
+ <action name="Helden van nu: Vrijwilligers in de gezondheidszorg" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:05 - 16:30 - </time></action>
+
+ <action name="Leven met verlies" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:30 - 17:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:00 - 17:10 - </time></action>
+
+ <action name="Schepper & co" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:10 - 17:35 - </time></action>
+
+ <action name="MAX & Catherine" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:35 - 18:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:30 - 18:55 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:55 - 19:25 - </time></action>
+
+ <action name="Ingang Oost" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>19:25 - 20:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:00 - 20:30 - </time></action>
+
+ <action name="Netwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:30 - 21:05 - </time></action>
+
+ <action name="Memories" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>21:05 - 22:05 - </time></action>
+
+ <action name="Keyzer & De Boer Advocaten" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:05 - 22:55 - </time></action>
+
+ <action name="NCRV Dokument: Een familie van vaders" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:55 - 23:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>23:50 - 00:20 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:20 - 00:50 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:50 - 06:45 - </time></action>
+
+
+BackHomeLogout</channel-right-now>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Nederland 1</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>Nederland 1</h1>
+
+<b>Monday 13th</b><br />
+<b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+<br />
+ 23:55 - 00:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Wintertijd</a><br />
+00:50 - 06:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nacht-tv: Netwerk herhalingen</a><br />
+06:45 - 06:59 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland in beweging</a><br />
+06:59 - 09:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+ 07:00 - 07:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+07:10 - 07:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+07:30 - 07:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+07:40 - 08:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+08:00 - 08:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+08:10 - 08:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+ 08:30 - 08:40 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+08:40 - 09:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Goedemorgen Nederland</a><br />
+09:00 - 09:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a><br />
+09:10 - 09:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland in beweging</a><br />
+09:30 - 09:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+That's the question</a><br />
+09:55 - 10:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Schoondochter gezocht</a><br />
+ 10:50 - 11:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Blauw bloed</a><br />
+11:15 - 12:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Appeltje voor de dorst</a><br />
+<i>12:00 - 12:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>12:10 - 12:35 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>12:35 - 12:57 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Voor alle fans: Drukwerk</a></i><br />
+<i>12:57 - 13:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Trekking Lingo</a></i><br />
+ <i>13:00 - 13:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>13:10 - 13:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Sportjournaal</a></i><br />
+<i>13:20 - 14:15 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Buitenhof</a></i><br />
+<i>14:15 - 14:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Hoge bomen in de misdaad</a></i><br />
+<i>14:55 - 15:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+AVRO Dierenpark</a></i><br />
+<i>15:20 - 16:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Kruispunt</a></i><br />
+ <i>16:00 - 16:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>16:05 - 16:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Helden van nu: Vrijwilligers in de gezondheidszorg</a></i><br />
+<i>16:30 - 17:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Leven met verlies</a></i><br />
+<i>17:00 - 17:10 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+<i>17:10 - 17:35 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Schepper & co</a></i><br />
+ <i>17:35 - 18:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+MAX & Catherine</a></i><br />
+<i>18:30 - 18:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+That's the question</a></i><br />
+<i>18:55 - 19:25 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>19:25 - 20:00 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Ingang Oost</a></i><br />
+<i>20:00 - 20:30 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NOS-Journaal</a></i><br />
+ <i>20:30 - 21:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Netwerk</a></i><br />
+<i>21:05 - 22:05 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Memories</a></i><br />
+<i>22:05 - 22:55 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Keyzer & De Boer Advocaten</a></i><br />
+<i>22:55 - 23:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+NCRV Dokument: Een familie van vaders</a></i><br />
+<i>23:50 - 00:20 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Blauw bloed</a></i><br />
+ <i>00:20 - 00:50 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Man bijt hond</a></i><br />
+<i>00:50 - 06:45 - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nacht-tv: Netwerk herhaling</a></i><br />
+<br />
+ <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=6&tz=1&sel=0&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:04, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<favorite-channels>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 1" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 2" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)1~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Nederland 3" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)2~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Net5" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)3~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL4" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)4~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="SBS6" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)5~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL5" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)6~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Discovery Channel"
+ type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)7~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="RTL7" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)8~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Veronica" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)9~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="MTV" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)10~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="TheBox" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)11~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="Eurosport" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)12~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="CNN" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)13~back)$tvhtml$tvstart.php(tz)2"/>
+ <action xmlns:xhtml="http://www.w3.org/1999/xhtml" name="BBC1" type="channel-overview"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)14~back)$tvhtml$tvstart.php(tz)2"
+ />
+</favorite-channels>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org"/>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1"/>
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css"/>
+ <link rel="shortcut icon" href="../favicon.ico"/>
+ <title>KiSS - Favorite Channels</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <h1>
+ <img src="../images/KiSS_Logo.gif" align="right"/>
+ <a href="tvstart.php">
+ <img border="0" src="../images/tvguide_logo.gif"/>
+ </a>
+ </h1>
+
+ <br/>
+
+
+ <table align="center" width="400" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td align="left">
+ <h1>Favorite Channels</h1>
+
+ <p/>
+ </td>
+ </tr>
+ </table>
+
+ <table align="center" style="width: 400; text-align: left;" border="0" cellpadding="0"
+ cellspacing="0">
+ <tbody>
+ <tr>
+ <td style="vertical-align: bottom; width: 400px;">
+ <img src="../images/tabs_none.gif" alt="" usemap="#tabs_Map" border="0"/>
+ <map id="tabs_Map" name="tabs_Map">
+ <area shape="poly" alt="What's On Now?" coords="126,30, 118,0, 0,0, 0,30"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?reload=1&mode=3&station=0&view=0&now=1"
+ class="grid"/>
+ <area shape="poly" alt="Favorite Shows" coords="239,0, 247,30, 128,30, 120,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ <area shape="poly" alt="Movies" coords="241,0, 250,30, 309,30, 300,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ <area shape="poly" alt="Sports" coords="302,0, 311,30, 370,30, 362,0"
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2"
+ class="grid"/>
+ </map>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table class="tv" align="center" width="400" border="0" cellspacing="1" cellpadding="1">
+ <tr height="25">
+ <td bgcolor="black" align="right" colspan="3">
+ <font color="white"> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=1&tz=2&back=$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowleft.gif" alt="Prev" border="0" align="texttop"/>
+ </a> <b>Page 1/2</b> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=1&tz=2&back=$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowright.gif" alt="Next" border="0" align="texttop"/>
+ </a> <br/>
+ </font>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/6_h30.gif" align="middle"
+ /> <b>NL1</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 1</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/2_h30.gif" align="middle"
+ /> <b>NL2</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 2</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)1~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/5_h30.gif" align="middle"
+ /> <b>NL3</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <b>Nederland 3</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)2~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/4_h30.gif" align="middle"
+ /> <b>NET5</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <b>Net5</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)3~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/9_h30.gif" align="middle"
+ /> <b>RTL4</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL4</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)4~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/7_h30.gif" align="middle"
+ /> <b>SBS6</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <b>SBS6</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)5~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/3_h30.gif" align="middle"
+ /> <b>RTL5</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL5</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)6~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/99_h30.gif" align="middle"
+ /> <b>DISC</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <b>Discovery Channel</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)7~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/8_h30.gif" align="middle"
+ /> <b>RTL7</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <b>RTL7</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)8~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/1_h30.gif" align="middle"
+ /> <b>VERO</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <b>Veronica</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)9~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/177_h30.gif" align="middle"
+ /> <b>MTV</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <b>MTV</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)10~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/665_h30.gif" align="middle"
+ /> <b>BOX</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <b>TheBox</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)11~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/94_h30.gif" align="middle"
+ /> <b>ESPO</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <b>Eurosport</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)12~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/470_h30.gif" align="middle"
+ /> <b>CNN</b></td>
+ <td align="left" class="listCell1">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <b>CNN</b>
+ </a>
+ </td>
+ <td align="center" class="listCell1"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)13~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+
+ <tr>
+ <td align="left" class="listCell3" width="21%"><img
+ src="http://epg.kml.kiss-technology.com/tv/logos/184_h30.gif" align="middle"
+ /> <b>BBC1</b></td>
+ <td align="left" class="listCell2">
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <b>BBC1</b>
+ </a>
+ </td>
+ <td align="center" class="listCell2"><a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=3&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowup_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=4&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/arrowdown_small.gif"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation_do.php?mode=3&tz=2&action=2&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~reload)1~sel)14~back)$tvhtml$tvstart.php(tz)2">
+ <img border="0" src="../images/delx_small.gif"/>
+ </a></td>
+ </tr>
+ </table>
+
+ <table align="center" width="400" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td><br/>
+ <br/>
+ <a href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=2">
+ <img src="../images/back.gif" alt="Back" border="0" align="middle"/>
+ </a><a href="tvstart.php">
+ <img src="../images/home.gif" alt="Home" border="0" align="middle"/>
+ </a><a href="../logout.php">
+ <img src="../images/logout.gif" alt="Logout" border="0" align="middle"/>
+ </a> 17:15, Wednesday 23rd August</td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<favorite-channels> << >><action name="Nederland 1"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Nederland 2"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Nederland 3"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Net5"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL4"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="SBS6"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL5"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)6~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Discovery Channel"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)7~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="RTL7"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Veronica"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="Eurosport"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="MTV"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="TheBox"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="CNN"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)13~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="BBC1"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)14~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/><action name="BBC2"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=185&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)15~back)$tvhtml$tvstart.php(tz)1"
+ type="channel-overview"/> << >>BackHomeLogout</favorite-channels>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Favorite Channels</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>Favorite Channels</h1>
+
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+<<</a> <b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+<br />
+ <b>NL1</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=6&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Nederland 1</a><br />
+<b>NL2</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=2&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Nederland 2</a><br />
+<b>NL3</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=5&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1">
+Nederland 3</a><br />
+ <b>NET5</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=4&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1">
+Net5</a><br />
+<b>RTL4</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=9&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1">
+RTL4</a><br />
+<b>SBS6</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=7&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1">
+SBS6</a><br />
+<b>RTL5</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=3&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)6~back)$tvhtml$tvstart.php(tz)1">
+RTL5</a><br />
+ <b>DISC</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=99&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)7~back)$tvhtml$tvstart.php(tz)1">
+Discovery Channel</a><br />
+<b>RTL7</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=8&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1">
+RTL7</a><br />
+<b>VERO</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=1&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1">
+Veronica</a><br />
+<b>ESPO</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=94&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1">
+Eurosport</a><br />
+ <b>MTV</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=177&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1">
+MTV</a><br />
+<b>BOX</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=665&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1">
+TheBox</a><br />
+<b>CNN</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=470&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)13~back)$tvhtml$tvstart.php(tz)1">
+CNN</a><br />
+<b>BBC1</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=184&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)14~back)$tvhtml$tvstart.php(tz)1">
+BBC1</a><br />
+ <b>BBC2</b> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&tz=1&station=185&back=$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)15~back)$tvhtml$tvstart.php(tz)1">
+BBC2</a><br />
+ <br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+<<</a> <b>Page 1/1</b> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?reload=1&mode=3&station=0&page=0&tz=1&back=$tvhtml$tvstart.php(tz)1">
+>></a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=1">Back</a> ] [ <a
+ href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:08, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Favorite Channels</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1> Favorite Channels</h1>
+
+<h2>What's on?</h2>
+
+<a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Right now</a> - 11:43, Monday 13th March<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=20&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Evening</a> - Starting 20:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=16&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)2~back)$tvhtml$tvstart.php(tz)1">
+Afternoon</a> - Starting 16:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=12&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)3~back)$tvhtml$tvstart.php(tz)1">
+Noon</a> - Starting 12:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=6&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)4~back)$tvhtml$tvstart.php(tz)1">
+Morning</a> - Starting 6:00<br />
+ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=36&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)5~back)$tvhtml$tvstart.php(tz)1">
+Tomorrow</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=60&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)8~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Wednesday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=84&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)9~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Thursday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=108&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)10~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Friday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=132&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)11~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Saturday</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&country=XX&station=0&view=156&tz=1&back=$tvhtml$tvstation.php(mode)3~station)0~tz)1~sel)12~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Sunday</a> <br />
+ <br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstart.php?tz=1">Back</a> ] [ <a
+ href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 11:43, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+ <head>\r
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />\r
+ <meta http-equiv="content-type"\r
+ content="text/html;charset=iso-8859-1" />\r
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css" />\r
+ <link rel='shortcut icon' href='../favicon.ico' />\r
+ <title>KiSS - TV Guide</title>\r
+ </head>\r
+ <body bgcolor="#ffffff">\r
+ <img src="../images/KiSS_Logo.gif" align="right" /><br />\r
+ <br />\r
+ <br />\r
+ \r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td align="left"><img src="../images/tvguide_logo.gif" /><br />\r
+ <br />\r
+ <br />\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td><b>Welcome</b> <u>sf2np2ln9no1</u> /\r
+ <u>web@brakkee.org</u> [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2">\r
+ Change</a> email ]<br />\r
+ <br />\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table class="tvstart" width="400" border="0" cellspacing="0"\r
+ cellpadding="1" align="center">\r
+ <tr>\r
+ <td align="center" height="25" bgcolor="black">\r
+ <div align="center"><font color="white"><b> Favorite\r
+ Channels </b></font></div>\r
+ </td>\r
+ <td align="center" bgcolor="black" width="10"> </td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font color="white"><b> Favorite\r
+ Shows </b></font></div>\r
+ </td>\r
+ <td align="center" bgcolor="black" width="10"> </td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font\r
+ color="white"><b> Movies </b></font></div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="10">\r
+ <td align="center" height="10"></td>\r
+ <td align="center" width="10" height="10"></td>\r
+ <td align="center" height="10"></td>\r
+ <td align="center" width="10" height="10"></td>\r
+ <td align="center" height="10"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on now?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Search a show</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center"></td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Favorites</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Favorites</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center" bgcolor="black">\r
+ <div align="center"><font\r
+ color="white"><b> Sports </b></font></div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ \r
+ <tr>\r
+ <td align="center"></td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">Add a favorite</a> </div>\r
+ </td>\r
+ <td align="center" width="10"></td>\r
+ <td align="center">\r
+ <div align="center"> <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"\r
+ class="grid">What's on?</a> </div>\r
+ </td>\r
+ </tr>\r
+ \r
+ <tr height="5">\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ <td align="center" width="10" height="5"></td>\r
+ <td align="center" height="5"></td>\r
+ </tr>\r
+ </table>\r
+ \r
+ <table align="center" width="400" border="0" cellspacing="0"\r
+ cellpadding="1">\r
+ <tr>\r
+ <td>\r
+ <p><br />\r
+ Recordings to be sent to the player: <b>0</b> [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2">\r
+ View</a> ] [ <a\r
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2">Manual\r
+ recording</a> ]<br />\r
+ <br />\r
+ </p>\r
+ \r
+ <hr />\r
+ <br />\r
+ <b>Time change:</b> In order to see the correct time in the KiSS TV\r
+ Guide, do the following:\r
+ \r
+ <ol>\r
+ <li>On your player press SETUP and set your timezone to reflect\r
+ summer time, for example for mainland Western Europe this should\r
+ be: "CEST (GMT+2)", UK and Ireland: "BST (GMT+1)", Finland and\r
+ Eastern Europe: "EEST (GMT+3)".</li>\r
+ \r
+ <li>Access the TV Guide at least once via your player.</li>\r
+ \r
+ <li>The KiSS TV Guide will now display the correct time for your tv\r
+ programs both via your player and through the web.</li>\r
+ </ol>\r
+ \r
+ <br />\r
+ <hr />\r
+ <br />\r
+ KML favorites: <b>0</b> [ <a\r
+ href="http://epg.kml.kiss-technology.com/favorites/favhtml.php?tz=2&back=$tvhtml$tvstart.php(tz)2">\r
+ View</a> ]<br />\r
+ <br />\r
+ <br />\r
+ <a href="../logout.php"><img src="../images/logout_solo.gif"\r
+ alt="logout" border="0" align="middle" /></a> 16:29,\r
+ Wednesday 23rd August</td>\r
+ </tr>\r
+ </table>\r
+ </body>\r
+</html>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<login>
+ <action name=" unknown Change"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2"
+ type=" unknown Change"/>
+ <action name=" unknown What's on now?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on now?"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown Favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Favorites"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown Search a show"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Search a show"/>
+ <action name=" unknown Favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Favorites"/>
+ <action name=" unknown Add a favorite"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown Add a favorite"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name=" unknown What's on?"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type=" unknown What's on?"/>
+ <action name="view-recordings"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ type="view-recordings"/>
+ <action name="manual-recording"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2"
+ type="manual-recording"/>
+ <action name=" unknown "
+ reference="../logout.php"
+ type=" unknown "/>
+</login>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy, see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<link rel="STYLESHEET" type="text/css" href="../kiss.css" />
+<link rel='shortcut icon' href='../favicon.ico' />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+
+<h1>TV Guide</h1>
+
+<table align="center" width="400" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td><b>Welcome</b> <u>sf2np2ln9no1</u> /
+<u>web@brakkee.org</u> [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=2">
+Change</a> email ]<br />
+<br />
+ </td>
+</tr>
+</table>
+
+<table class="tvstart" width="100%" border="0" cellspacing="0"
+cellpadding="1" align="center">
+<tr>
+<td align="center" height="25" bgcolor="black">
+<div align="center"><font color="white"><b> Favorite
+Channels </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on now?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Favorites</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font color="white"><b> Favorite
+Shows </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Search a show</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Favorites</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">Add a favorite</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font
+color="white"><b> Movies </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="5">
+<td align="center" height="5"></td>
+</tr>
+
+<tr>
+<td align="center" bgcolor="black">
+<div align="center"><font
+color="white"><b> Sports </b></font></div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center">
+<div align="center"> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=2&back=$tvhtml$tvstart.php(tz)2"
+ class="grid">What's on?</a> </div>
+</td>
+</tr>
+
+<tr height="10">
+<td align="center" height="10"></td>
+</tr>
+
+<tr>
+<td align="center"></td>
+</tr>
+</table>
+
+<table align="center" width="100%" border="0" cellspacing="0"
+cellpadding="1">
+<tr>
+<td>
+<p><br />
+Recordings to be sent to the player: 0 [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=2&back=$tvhtml$tvstart.php(tz)2">
+View</a> ] [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=2">Manual
+recording</a> ]<br />
+<br />
+ <a href="../logout.php"><img src="../images/logout_small.gif"
+alt="logout" border="0" align="center" /></a> 22:19, Monday
+21st August</p>
+</td>
+</tr>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<login>
+ <action
+ name=" unknown Change"
+ type=" unknown Change"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=1"/>
+ <action name="channels-whats-on-now" type="channels-whats-on-now"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="channels-whats-on" type="channels-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="channels-favorites" type="channels-favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-whats-on" type="shows-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-search" type="shows-search"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-favorites" type="shows-favorites"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="shows-add-favorite" type="shows-add-favorite"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="movies-whats-on" type="movies-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="sports-whats-on" type="sports-whats-on"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="view-recordings" type="view-recordings"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvstart.php(tz)1"/>
+ <action name="manual-recording" type="manual-recording"
+ reference="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=1"/>
+ <action name="logout" type="logout" reference="../logout.php"/>
+</login>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />
+ <meta http-equiv="content-type"
+ content="text/html;charset=iso-8859-1" />
+ <title>KiSS - TV Guide</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <img src="../images/KiSS_Logo_small.gif" align="center" />
+
+ <h1>TV Guide</h1>
+
+ <b>Welcome</b> <u>sf2np2ln9no1</u> /
+ <u>erik@brakkee.org</u> [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvemailset.php?tz=1">
+ Change</a> email ]<br />
+ <br />
+
+
+ <h2>Favorite Channels</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvshow.php?mode=3&station=0&view=0&now=1&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on now?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvstation.php?mode=3&station=0&country=XX&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tv.php?mode=3&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Favorites</a>
+
+ <h2>Favorite Shows</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=6&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Search a show</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=8&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Favorites</a><br />
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ Add a favorite</a>
+
+ <h2>Movies</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=7&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a>
+
+ <h2>Sports</h2>
+
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvfavshow_action.php?action=9&tz=1&back=$tvhtml$tvstart.php(tz)1">
+ What's on?</a>
+
+ <p><br />
+ Recordings to be sent to the player: 0<br />
+ [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvstart.php(tz)1">
+ View</a> ] [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvmanual.php?tz=1">Manual
+ recording</a> ]<br />
+ <br />
+ [ <a href="../logout.php">Logout</a> ]<br />
+ 21:09, Sunday 12th March</p>
+ </body>
+ </html>
+
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org" />
+ <meta content="text/html;charset=iso-8859-1"
+ http-equiv="content-type" />
+ <link href="kiss.css" type="text/css" rel="STYLESHEET" />
+ <link rel='shortcut icon' href='favicon.ico' />
+ <title>KiSS Technology Online Portal</title>
+ <script type="text/javascript" language="JavaScript">
+ //<![CDATA[
+ function placeFocus() {
+ if (document.forms.length > 0) {
+ var field = document.forms[0];
+ for (i = 0; i < field.length; i++) {
+ if ((field.elements[i].type == "text") || (field.elements[i].type == "textarea") || (field.elements[i].type.toString().charAt(0) == "s")) {
+ document.forms[0].elements[i].focus();
+ break; } } } }
+ function placeFocusP() {
+ if (document.forms.length > 0) {
+ var field = document.forms[0];
+ for (i = 0; i < field.length; i++) {
+ if ((field.elements[i].type == "password")) {
+ document.forms[0].elements[i].focus();
+ break; } } } }
+ //]]>
+ </script>
+ </head>
+ <body bgcolor="#ffffff" onload="placeFocus()">
+ <div align="center">
+ <h2><img src="images/kiss_logo_login.gif"
+ align="middle" /> Web Services:<br />
+ <img src="images/tvguide_logo.gif" align="middle" /><br />
+ </h2>
+ </div>
+
+ <table align="center" border="0" cellpadding="0" cellspacing="0"
+ style="width: 240px;">
+ <tbody>
+ <tr>
+ <td style="text-align: left;">
+ <h2> Login</h2>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <br />
+
+
+ <form name="FormName" method="post" action="login_core.php"
+ id="FormName"><input type="hidden" name="token"
+ value="e1c6b500600a0a2ba585ae52338a817f" />
+
+ <table align="center" border="0" cellpadding="0" cellspacing="0"
+ background="images/login_background.gif"
+ style="width: 240px; height: 240px;">
+ <tbody>
+ <tr>
+ <td style="vertical-align: top;"><br />
+ </td>
+ <td style="vertical-align: top;"><br />
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align: right; width: 100px;">Player ID <br />
+ or Email: </td>
+ <td style="width: 180px;"><input type="text" maxlength="50"
+ size="20" name="user" value="" /> *<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td style="text-align: right; width: 100px;">Password: </td>
+ <td><input type="password" maxlength="10" size="8" name="passwd" />
+ **<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="checkbox" value="1" name="SavePlayerID" /> Save
+ PlayerID ***<br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td> </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="radio" checked="checked" value="GraphicMode"
+ name="GMode" />Desktop mode<br />
+ <input type="radio" value="MobileMode" name="GMode" />Mobile
+ mode<br />
+ <input type="radio" value="TextMode" name="GMode" />Text mode</td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><br />
+ </td>
+ </tr>
+
+ <tr>
+ <td width="100"><br />
+ </td>
+ <td><input type="submit" value="Login" name="submit" /></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><br />
+ </td>
+ <td style="vertical-align: top;"><br />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </form>
+
+ <center>[ <a href="l.php">Text version</a> ]</center>
+
+ <br />
+
+
+ <table align="center" style="text-align: center; width: 600px;"
+ border="0" cellspacing="2" cellpadding="2">
+ <tbody>
+ <tr>
+ <td style="vertical-align: top;"><font size="1">* You can find your
+ player id by pressing Menu on your remote control > Online
+ KML Services > Reveal PlayerID.<br />
+ You can use your email address instead of player id only when you
+ have already logged in once and associated your email address with
+ your player id.</font></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><font size="1">** You must have
+ already configured your password by going to the EPG start page
+ > Configure > Set Password</font></td>
+ </tr>
+
+ <tr>
+ <td style="vertical-align: top;"><font size="1">*** Saving player
+ id / email requires cookies</font></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <br />
+ </body>
+ </html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><program-info>IMDbWhen is it on?<title> KRO Filmtheater: Hollywood ending </title><keywords> Film </keywords><description>Komisch filmdrama De regisseur Val Waxman was ooit erg succesvol.
+ Tegenwoordig regisseert hij echter alleen nog maar tv-commercials. Eindelijk krijgt hij weer
+ eens een aanbod om een grote film te maken. Het lot wil echter dat Val op dat moment tijdelijk
+ blind wordt, als resultaat van zijn paranoia. Hij probeert samen met enkele vrienden op de set
+ zijn handicap te verbergen.</description></program-info>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="generator" content="HTML Tidy, see www.w3.org"/>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1"/>
+ <meta name="generator" content="Adobe GoLive"/>
+ <link rel="STYLESHEET" type="text/css" href="../kiss.css"/>
+ <link rel="shortcut icon" href="../favicon.ico"/>
+ <title>KiSS - Program info</title>
+ </head>
+ <body bgcolor="#ffffff">
+ <img src="../images/KiSS_Logo_small.gif" align="center"/>
+
+ <h1> Program info</h1>
+
+ <table class="tvinfo" align="center" border="0" cellspacing="0" cellpadding="1" width="100%">
+ <tr>
+ <td colspan="3" align="left" bgcolor="black" width="50%">
+ <font size="+1" color="white">
+ <b> KRO Filmtheater: Hollywood ending </b>
+ </font>
+ </td>
+ <td width="10" bgcolor="black"/>
+ <td colspan="3" align="right" bgcolor="black">
+ <font size="+1" color="white">
+ <b> Nederland 1 <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1777728675&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowleft_small.gif" alt="Prev" border="0" align="middle"/>
+ </a> <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1777728677&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/arrowright_small.gif" alt="Next" border="0" align="middle"/>
+ </a> </b>
+ </font>
+ </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td colspan="3" align="center" bgcolor="#B3C5D9" width="50%">
+ <font color="#000000">Sunday 20th August</font>
+ </td>
+ <td width="10"/>
+ <td class="listCell1" colspan="3" align="center" bgcolor="#cccccc">
+ Film </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td align="center" bgcolor="#B3C5D9">
+ <font color="#000000">22:30 - 00:15</font>
+ </td>
+ <td width="10"/>
+ <td class="listCell1" align="center" bgcolor="#cccccc">(01:45 hours)</td>
+ <td width="10"/>
+ <td class="listCell1" colspan="3" align="center">
+ <div align="center"> </div>
+ </td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td colspan="7" align="left">Komisch filmdrama De regisseur Val Waxman was ooit erg succesvol.
+ Tegenwoordig regisseert hij echter alleen nog maar tv-commercials. Eindelijk krijgt hij weer
+ eens een aanbod om een grote film te maken. Het lot wil echter dat Val op dat moment tijdelijk
+ blind wordt, als resultaat van zijn paranoia. Hij probeert samen met enkele vrienden op de set
+ zijn handicap te verbergen.</td>
+ </tr>
+
+ <tr height="5">
+ <td align="center" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ <td align="center" width="10" height="5"/>
+ <td align="left" height="5"/>
+ </tr>
+
+ <tr>
+ <td class="listCell1" colspan="7" align="left" valign="middle"> [ <a target="_blank"
+ href="http://www.imdb.com/find?q=KRO+Filmtheater%3A+Hollywood+ending" class="grid"
+ >IMDb</a> ] [ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch_show.php?tz=2&tvshow=KRO+Filmtheater%3A+Hollywood+ending&back=$tvhtml$tvinfo.php(tz)2~id)1777728676~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)2~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2"
+ class="grid">When is it on?</a> ] </td>
+ </tr>
+ </table>
+
+ <table align="center" width="100%" border="0" cellspacing="0" cellpadding="1">
+ <tr>
+ <td><br/>
+ <br/>
+ <a
+ href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&day=0&page=0&tz=2&progid=0&sel=0&back=$tvhtml$tvstation.php(mode)3~station)6~tz)2~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)2~sel)0~back)$tvhtml$tvstart.php(tz)2">
+ <img src="../images/back_small.gif" alt="Back" border="0" align="center"/>
+ </a> <a href="tvstart.php">
+ <img src="../images/home_small.gif" alt="Home" border="0" align="center"/>
+ </a> <a href="../logout.php">
+ <img src="../images/logout_small.gif" alt="Logout" border="0" align="center"/>
+ </a> 23:50, Monday 21st August</td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><program-info>
+<<
+>>IMDb<action name="record" reference="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_add.php?id=1772395857&tz=1&back=$tvhtml$tvinfo.php(station)6~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1" type="recorded">
+
+ </action>When is it on?
+BackHomeLogout<title>Kruispunt</title><keywords>
+ Religieus</keywords><description>
+
+ Achtergronden uit kerk en samenleving.
+
+</description></program-info>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - Program info</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1> Program info</h1>
+
+<h2>Kruispunt</h2>
+
+<h2>Nederland 1 <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+<<</a> <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?reload=1&id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+>></a></h2>
+
+<b>Monday 13th March</b><br />
+ Religieus<br />
+ <b>15:20 - 16:00</b> (40 minutes)<br />
+ <br />
+ Achtergronden uit kerk en samenleving.<br />
+ <br />
+<br />
+[ <a target="_blank"
+href="http://www.imdb.com/find?q=Kruispunt"
+class="grid">IMDb</a> ] [ <img
+src="../images/record_dot.gif" align="middle" hspace="2" /><a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_add.php?id=1772395857&tz=1&back=$tvhtml$tvinfo.php(station)6~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">Record</a> ] [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvsearch_show.php?tz=1&tvshow=Kruispunt&back=$tvhtml$tvinfo.php(tz)1~id)1772395857~back)$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"
+ class="grid">When is it on?</a> ] <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?channel=6&day=0&page=0&tz=1&progid=0&sel=0&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 12:16, Monday 13th March
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<link rel="STYLESHEET" type="text/css"
+href="http://epg.kml.kiss-technology.com/kiss.css" />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<h1><img
+src="http://epg.kml.kiss-technology.com/images/KiSS_Logo.gif"
+align="middle" />Error</h1>
+
+Show is already in the recording queue!<br />
+<br />
+[ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=6&id=1772583278&back=$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ]<br />
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<link rel="STYLESHEET" type="text/css"
+href="http://epg.kml.kiss-technology.com/kiss.css" />
+<title>KiSS - TV Guide</title>
+</head>
+<body bgcolor="#ffffff">
+<h1><img
+src="http://epg.kml.kiss-technology.com/images/KiSS_Logo.gif"
+align="middle" />Error</h1>
+
+This show conflicts with a recording that is already in the <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_show.php?tz=1&back=$tvhtml$tvinfo.php(station)2~id)1772583335~back)$tvhtml$tvlist.php(channel)2~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)2~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+recording queue</a>!<br />
+<br />
+[ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=2&id=1772583335&back=$tvhtml$tvlist.php(channel)2~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)2~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)1~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ]<br />
+</body>
+</html>
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator"
+content="HTML Tidy for Linux/x86 (vers 1st April 2002), see www.w3.org" />
+<meta http-equiv="content-type"
+content="text/html;charset=iso-8859-1" />
+<title>KiSS - TV Guide - Recordings</title>
+</head>
+<body bgcolor="#ffffff">
+<img src="../images/KiSS_Logo_small.gif" align="center" />
+<h1>TV Guide - Recordings</h1>
+
+<h2>Recordings already sent to player</h2>
+
+<b>SBS6</b> - Lois & Clark: The new adventures of Superman -
+ 08:00 - 09:00 -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541086&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <b>VERO</b> - Brainiac -
+<i> 19:40 - 20:10 </i> -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541060&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+<b>VERO</b> - Brainiac - <i> 19:40 - 20:10 </i>
+- <i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772541060&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+ <b>DISC</b> - Brainiac -
+<i> 22:00 - 23:00 </i> -
+<i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772540925&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+<b>DISC</b> - Brainiac - <i> 22:00 - 23:00 </i>
+- <i> Fri 17th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772540925&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a> - Error (Recording conflict)<br />
+ <b>NL3</b> - The Kumars at no. 42 -
+<i> 15:55 - 16:26 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583485&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+<b>VERO</b> - Stargate SG-1 -
+<i> 18:10 - 18:55 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583562&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <b>VERO</b> - Battlestar Galactica -
+<i> 19:35 - 20:25 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583564&from=0&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <br />
+
+<h2>Recordings to be sent to player</h2>
+
+<b>NL1</b> - Samen tegen Kanker -
+<i> 20:30 - 22:25 </i> -
+<i> Sat 18th </i> - <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvrecord_del.php?id=1772583278&from=1&back=$tvhtml$tvrecord_show.php(tz)1~back)$tvhtml$tvinfo.php(station)6~id)1772583278~back)$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Delete</a><br />
+ <br />
+<br />
+ [ <a
+href="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?station=6&id=1772583278&back=$tvhtml$tvlist.php(channel)6~day)1~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)5~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1">
+Back</a> ] [ <a
+href="tvstart.php">Home</a> ] [ <a
+href="../logout.php">Logout</a> ]<br />
+ 19:46, Friday 17th March
+</body>
+</html>
+
--- /dev/null
+<report>
+
+ <messages>
+ <message>Hello world!</message>
+ <message>and another message</message>
+ </messages>
+
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent.</description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<!-- dependencies of the kiss crawler itself -->
+
+<target name="kisscrawler.src.d"
+ depends="logging.d,mail.d,commons-email.d,commons-beanutils.d,commons-codec.d,dom4j.d,xerces.d,httpclient.d,jtidy.d,wamblee.support.d,wamblee.crawler.d,spring.d">
+</target>
+
+<target name="kisscrawler.test.d" depends="wamblee.support.test.d,wamblee.crawler.test.d">
+</target>
+
+
+<!-- dependency to use for depending on the kiss crawler -->
+
+<property name="kisscrawler.dist.dir" value="${lib.dir}/wamblee/crawler/kiss"/>
+<target name="wamblee.kisscrawler.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${kisscrawler.dist.dir}">
+ <include name="wamblee-crawler-kiss.jar"/>
+ </fileset>
+ </copy>
+</target>
+<target name="wamblee.kisscrawler.test.d">
+ <copy todir="${download.dir}">
+ <fileset dir="${kisscrawler.dist.dir}">
+ <include name="wamblee-crawler-kiss-test.jar"/>
+ </fileset>
+ </copy>
+</target>
--- /dev/null
+This is the base documentation directory.
+
+skinconf.xml # This file customizes Forrest for your project. In it, you
+ # tell forrest the project name, logo, copyright info, etc
+
+sitemap.xmap # Optional. This sitemap is consulted before all core sitemaps.
+ # See http://forrest.apache.org/docs/project-sitemap.html
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+#=======================================================================
+# CatalogManager.properties for Catalog Entity Resolver.
+#
+# This is the default properties file for your project.
+# This facilitates local configuration of application-specific catalogs.
+# If you have defined any local catalogs, then they will be loaded
+# before Forrest's core catalogs.
+#
+# See the Apache Forrest documentation:
+# http://forrest.apache.org/docs/your-project.html
+# http://forrest.apache.org/docs/validation.html
+
+# verbosity:
+# The level of messages for status/debug (messages go to standard output).
+# The setting here is for your own local catalogs.
+# The verbosity of Forrest's core catalogs is controlled via
+# main/webapp/WEB-INF/cocoon.xconf
+#
+# The following messages are provided ...
+# 0 = none
+# 1 = ? (... not sure yet)
+# 2 = 1+, Loading catalog, Resolved public, Resolved system
+# 3 = 2+, Catalog does not exist, resolvePublic, resolveSystem
+# 10 = 3+, List all catalog entries when loading a catalog
+# (Cocoon also logs the "Resolved public" messages.)
+verbosity=1
+
+# catalogs ... list of additional catalogs to load
+# (Note that Apache Forrest will automatically load its own default catalog
+# from main/webapp/resources/schema/catalog.xcat)
+# Use either full pathnames or relative pathnames.
+# pathname separator is always semi-colon (;) regardless of operating system
+# directory separator is always slash (/) regardless of operating system
+catalogs=../resources/schema/catalog.xcat
+
+# relative-catalogs
+# If false, relative catalog URIs are made absolute with respect to the
+# base URI of the CatalogManager.properties file. This setting only
+# applies to catalog URIs obtained from the catalogs property in the
+# CatalogManager.properties file
+# Example: relative-catalogs=[yes|no]
+relative-catalogs=no
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<html>
+ <head>
+ <title>Raw un-processed HTML page (test1)</title>
+ </head>
+ <body>
+ <h1>raw un-processed HTML page (test1)</h1>
+ <p>
+ This raw HTML page is linked to from xdocs/samples/static.xml
+ and from xdocs/samples/linking.xml
+ </p>
+ <p>All linked-to pages (for example:
+ <a href="test2.html"><a href="test2.html"></a>) are
+ also available.
+ </p>
+ <hr />
+ <p>
+ [return to <a href="index.html">Index</a>]<br>
+ [return to <a href="samples/linking.html">Linking demonstration</a>]
+ </p>
+ </body>
+</html>
--- /dev/null
+<html>
+<head>
+<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>KiSS crawler report</title>
+</head>
+<body>
+<h1>KiSS crawler report</h1>
+<h2>Successfully recorded programs <p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>18:00 - 18:55: <strong>Stargate SG-1</strong> (Veronica/Serie/soap)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Sf-serie SG-1 krijgt een aanbod van een buitenaardse wereld voor een wondermedicijn.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>19:25 - 20:25: <strong>Stargate SG-1</strong> (Veronica/Serie/soap)</td>
+</tr>
+<tr>
+
+<td>
+<blockquote>
+<font size="-1">Sf-serie Tijdens een vlucht van de nieuwe X-303, codenaam Prometheus, wordt het schip overmand door NID-agenten.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</h2>
+<h2>Conflicts with other recorded programs<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>20:00 - 20:45: <strong>Doctor Who</strong> (BBC1/Drama)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Madame de Pompadour finds the court at Versailles under attack from sinister clockwork killers. Her only hope of salvation lies with the man who has haunted her dreams since childhood - a mysterious stranger known only as the Doctor.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</h2>
+<h2>Possibly interesting programs</h2>
+<p>
+<table cellpadding="5" align="left"></table>
+
+<br clear="left">
+</p>
+<h3>Category: documentaires</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>11:50 - 12:30: <strong>Zembla</strong> (Nederland 3/Documentaire)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1"></font>
+
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>13:10 - 13:35: <strong>Andere tijden</strong> (Nederland 3/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Geschiedenisrubriek met reportages over (bijna) vergeten gebeurtenissen uit de twintigste eeuw. De redactie gaat op zoek naar ooggetuigen en betrokkenen en naar historische filmbeelden om aan de hand daarvan de verhalen van vroeger opnieuw te vertellen.</font>
+</blockquote>
+</td>
+
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: films</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>13:10 - 13:35: <strong>Andere tijden</strong> (Nederland 3/Film)</td>
+</tr>
+<tr>
+<td>
+
+<blockquote>
+<font size="-1">Geschiedenisrubriek met reportages over (bijna) vergeten gebeurtenissen uit de twintigste eeuw. De redactie gaat op zoek naar ooggetuigen en betrokkenen en naar historische filmbeelden om aan de hand daarvan de verhalen van vroeger opnieuw te vertellen.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>22:00 - 06:00: <strong>Face off</strong> (Veronica/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Actiefilm Een FBI-agent wil kost wat het kost een psychotische terrorist pakken, die verantwoordelijk is voor de moord op zijn zoontje. In de strijd om de terrorist in te rekenen raakt deze in een coma. Hij heeft de agent echter nog net kunnen vertellen dat ergens in Los Angeles een bom verborgen ligt. Om op het spoor van deze bom te komen vragen regeringsfunctionarissen of de FBI-agent zich uit wil geven als de moordenaar van zijn zoon. Door middel van een medische ingreep worden de gezichten van beiden verwisseld met alle gevolgen van dien.</font>
+
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>23:25 - 01:00: <strong>Gossip</strong> (Net5/Film)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Thriller Drie studenten doen voor een schoolproject een proef over roddelen. Ze verspreiden een gerucht om te zien hoe lang het duurt voordat deze zich heeft verspreid. Maar wat begint als een onschuldige roddel, escaleert tot een groot misverstand en leidt zelfs tot een arrestatie wegens verkrachting. Het drietal beseft dat hun vooropgezete plan desastreuze gevolgen heeft en dat hun experiment niet meer te stoppen is.</font>
+</blockquote>
+</td>
+
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: science fiction</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>08:00 - 09:00: <strong>Lois & Clark: The new adventures of Superman</strong> (SBS6/Serie/soap)</td>
+</tr>
+
+<tr>
+<td>
+<blockquote>
+<font size="-1">Sf-serie Lex Luthor ontwikkelt verschillende tests om de kracht van Superman te doorgronden.</font>
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+<h3>Category: wetenschap</h3>
+<p>
+<table cellpadding="5" align="left">
+<tr align="left">
+<td>11:30 - 12:25: <strong>Triumph of life</strong> (RTL4/Documentaire)</td>
+
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Serie documentaires over de evolutie. Al het leven op aarde is ooit ontstaan uit een organisme dat zich door omstandigheden heeft kunnen ontwikkelen tot een wezen dat zich wist voort te planten. Dit ingewikkelde proces voltrok zich miljarden jaren geleden en is sindsdien gaande. Het werd in de negentiende eeuw voor het eerst in kaart gebracht door de Britse bioloog Charles Darwin. Sindsdien wordt de evolutietheorie wetenschappelijk onderzocht en betwijfeld, maar het feit is dat het leven zich in diverse vormen blijft ontwikkelen.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:00 - 00:30: <strong>Sex sense: Bi way</strong> (Discovery Channel/Wetenschap)</td>
+</tr>
+<tr>
+
+<td>
+<blockquote>
+<font size="-1">Documentaireserie Onderzoek naar de wetenschap van seksualiteit, gecombineerd met levendige beelden en ondeugende humor.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:30 - 01:00: <strong>Sex sense: Baring it all</strong> (Discovery Channel/Wetenschap)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+
+<font size="-1">Documentaireserie Onderzoek naar de wetenschap van seksualiteit, gecombineerd met levendige beelden en ondeugende humor.</font>
+</blockquote>
+</td>
+</tr>
+<tr align="left">
+<td>00:40 - 02:10: <strong>Top secret!</strong> (SBS6/Comedy)</td>
+</tr>
+<tr>
+<td>
+<blockquote>
+<font size="-1">Filmkomedie De knappe jaren '50 rock-'n-roll-ster Nick Rivers is in Oost-Duitsland om op te treden. Daar wordt hij verliefd op de dochter van een ontvoerde wetenschapper en komt hij in contact met het Franse verzet.</font>
+
+</blockquote>
+</td>
+</tr>
+</table>
+<br clear="left">
+</p>
+</body>
+</html>
--- /dev/null
+%PDF-1.3
+%ª«¬
+4 0 obj
+<< /Type /Info
+/Producer (FOP 0.20.4) >>
+endobj
+5 0 obj
+<< /Length 203 /Filter [ /ASCII85Decode /FlateDecode ]
+ >>
+stream
+Gar'!]afWZ&;9q-MRA)RFnblL2&]tQSZsjOOT[ck2SQkp(bfQ[R7ZPq=U24c0dqq_i?B[A.0s\)5f5<IA'lb0eeo`C+`q\Ip/Tke*)7%T+.hT8:QQidXoPLKZM,RXY"bP+;E@%,ZX;V'Aq+M9rH"!g=N5TToDMoqMeUiEe).I_W3q80:jF+;'9bVIeBRb]DhE9:E2be2~>
+endstream
+endobj
+6 0 obj
+<< /Type /Page
+/Parent 1 0 R
+/MediaBox [ 0 0 595 842 ]
+/Resources 3 0 R
+/Contents 5 0 R
+>>
+endobj
+7 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F1
+/BaseFont /Helvetica
+/Encoding /WinAnsiEncoding >>
+endobj
+8 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F5
+/BaseFont /Times-Roman
+/Encoding /WinAnsiEncoding >>
+endobj
+1 0 obj
+<< /Type /Pages
+/Count 1
+/Kids [6 0 R ] >>
+endobj
+2 0 obj
+<< /Type /Catalog
+/Pages 1 0 R
+ >>
+endobj
+3 0 obj
+<<
+/Font << /F1 7 0 R /F5 8 0 R >>
+/ProcSet [ /PDF /ImageC /Text ] >>
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000687 00000 n
+0000000745 00000 n
+0000000795 00000 n
+0000000015 00000 n
+0000000071 00000 n
+0000000365 00000 n
+0000000471 00000 n
+0000000578 00000 n
+trailer
+<<
+/Size 9
+/Root 2 0 R
+/Info 4 0 R
+>>
+startxref
+883
+%%EOF
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+ SVG Anteater logo
+
+To get started with SVG, I'd recommend getting the Adobe SVG plugin, and the
+xml-batik CVS module. Then have a look at the xml-batik/samples files. Use the
+SVG spec (http://www.w3.org/TR/SVG/) as a reference.
+-->
+
+<!-- See Forrest Issue: FOR-229
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg xmlns:for CDATA #FIXED "http://apache.org/forrest">
+ <!ENTITY % textExt "|for:group-name">
+ <!ELEMENT for:group-name (#PCDATA)>
+]>
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xsl:version="1.0"
+ xmlns:for="http://apache.org/forrest"
+ width="220" height="65" >
+ <title>Anteater logo</title>
+
+ <defs>
+
+ <!--
+ <radialGradient id="radialGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </linearGradient>
+ -->
+
+ <linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
+ <stop style="stop-color:white" offset="0"/>
+ <stop style="stop-color:lightgreen" offset="1"/>
+ </linearGradient>
+
+ <filter id="shadowFilter" filterUnits="objectBoundingBox" width="1.4" height="1.4">
+ <!-- Takes the alpha channel (black outline of the text), blurs it and saves as 'blur' -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="2 2" result="blur"/>
+ <!-- Takes saved 'blur' and offsets it by 4 pixels, saves as 'offsetBlur' -->
+ <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <!-- Merges SourceGraphic (original image) and 'offsetBlur', putting the
+ former 'over' the latter, and using the merged result as the finished
+ image -->
+ <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/>
+ </filter>
+
+ </defs>
+
+ <g filter="url(#shadowFilter)" fill="url(#gradient)">
+ <text x="40%" y="60%" style="font-size:24pt; font-family:Verdana ; text-anchor: middle">
+ <for:group-name />
+ </text>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+ SVG Anteater logo
+
+To get started with SVG, I'd recommend getting the Adobe SVG plugin, and the
+xml-batik CVS module. Then have a look at the xml-batik/samples files. Use the
+SVG spec (http://www.w3.org/TR/SVG/) as a reference.
+-->
+
+<!-- See Forrest Issue: FOR-229
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"
+[
+ <!ATTLIST svg xmlns:for CDATA #FIXED "http://apache.org/forrest">
+ <!ENTITY % textExt "|for:project-name">
+ <!ELEMENT for:project-name (#PCDATA)>
+]>
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xsl:version="1.0"
+ xmlns:for="http://apache.org/forrest"
+ width="420" height="65" >
+ <title>Anteater logo</title>
+
+ <defs>
+
+ <!--
+ <radialGradient id="radialGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient">
+ <stop style="stop-color:gold" offset="0"/>
+ <stop style="stop-color:orange" offset=".5"/>
+ <stop style="stop-color:crimson" offset="1"/>
+ </linearGradient>
+ -->
+
+ <linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
+ <stop style="stop-color:white" offset="0"/>
+ <stop style="stop-color:lightgreen" offset="1"/>
+ </linearGradient>
+
+ <filter id="shadowFilter" filterUnits="objectBoundingBox" width="1.4" height="1.4">
+ <!-- Takes the alpha channel (black outline of the text), blurs it and saves as 'blur' -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="2 2" result="blur"/>
+ <!-- Takes saved 'blur' and offsets it by 4 pixels, saves as 'offsetBlur' -->
+ <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <!-- Merges SourceGraphic (original image) and 'offsetBlur', putting the
+ former 'over' the latter, and using the merged result as the finished
+ image -->
+ <feComposite in="SourceGraphic" in2="offsetBlur" operator="over"/>
+ </filter>
+
+ </defs>
+
+ <g filter="url(#shadowFilter)" fill="url(#gradient)">
+ <text x="100%" y="60%" style="font-size:24pt; font-family:Verdana ; text-anchor: end" >
+ <for:project-name />
+ </text>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Automatic Recording for KiSS Hard Disk Recorders</title>
+ </header>
+ <body>
+ <warning> KiSS makes regular updates to their site that sometimes require adaptations to the
+ crawler. If it stops working, check out the most recent version here. </warning>
+ <section id="changelog">
+ <title>Changelog</title>
+ <section>
+ <title>21 November 2006</title>
+ <ul>
+ <li>Corrected the <code>config.xml</code> again.</li>
+ <li>Corrected errors in the documentation for the web application. It starts running at 19:00
+ and not at 5:00.</li>
+ </ul>
+ </section>
+ <section>
+ <title>19 November 2006</title>
+ <ul>
+ <li>Corrected the <code>config.xml</code> file to deal with changes in the login procedure.</li>
+ </ul>
+ </section>
+ <section>
+ <title>17 November 2006</title>
+ <ul>
+ <li>Corrected the packed distributions. The standalone distribution had an error in the
+ scripts and was missing libraries </li>
+
+ </ul>
+ </section>
+ <section>
+ <title>7 September 2006</title>
+ <ul>
+ <li>KiSS modified the login procedure. It is now working again.</li>
+ <li>Generalized the startup scripts. They should now be insensitive to the specific
+ libraries used. </li>
+ </ul>
+ </section>
+ <section>
+ <title>31 August 2006</title>
+ <ul>
+ <li>Added windows bat file for running the crawler under windows. Very add-hoc, will be
+ generalized. </li>
+ </ul>
+ </section>
+ <section>
+ <title>24 August 2006</title>
+ <ul>
+ <li>The crawler now uses desktop login for crawling. Also, it is much more efficient since
+ it no longer needs to crawl the individual programs. This is because the channel page
+ includes descriptions of programs in javascript popups which can be used by the crawler.
+ The result is a significant reduction of the load on the KiSS EPG site. Also, the delay
+ between requests has been increased to further reduce load on the KiSS EPG site. </li>
+ <li> The crawler now crawls programs for tomorrow instead of for today. </li>
+ <li> The web based crawler is configured to run only between 7pm and 12pm. It used to run
+ at 5am. </li>
+ </ul>
+ </section>
+
+ <section>
+ <title>13-20 August 2006</title>
+ <p> There were several changes to the login procedure, requiring modifications to the
+ crawler. </p>
+ <ul>
+ <li>The crawler now uses the 'Referer' header field correctly at login.</li>
+ <li>KiSS now uses hidden form fields in their login process which are now also handled
+ correctly by the crawler.</li>
+ </ul>
+ </section>
+ </section>
+ <section id="overview">
+ <title>Overview</title>
+
+ <p> In 2005, <a href="site:links/kiss">KiSS</a> introduced the ability to schedule recordings
+ on KiSS hard disk recorder (such as the DP-558) through a web site on the internet. When a
+ new recording is scheduled through the web site, the KiSS recorder finds out about this new
+ recording by polling a server on the internet. This is a really cool feature since it
+ basically allows programming the recorder when away from home. </p>
+ <p> After using this feature for some time, I started noticing regular patterns. Often you
+ are looking for the same programs and for certain types of programs. So, wouldn't it be nice
+ to have a program do this work for you and automatically record programs and notify you of
+ possibly interesting ones? </p>
+ <p> This is where the KiSS crawler comes in. This is a simple crawler which logs on to the
+ KiSS electronic programme guide web site and gets programme information from there. Then
+ based on that it automatically records programs for you or sends notifications about
+ interesting ones. </p>
+ <p> In its current version, the crawler can be used in two ways: </p>
+ <ul>
+ <li><strong>standalone program</strong>:
+ A standalone program run from the command-line or as a scheduled task.</li>
+ <li><strong>web application</strong>: A web application running on a java application
+ server. With this type of use, the crawler also features an automatic retry mechanism in
+ case of failures, as well as a simple web interface. </li>
+ </ul>
+ </section>
+
+ <section>
+ <title>Downloading</title>
+
+ <p> At this moment, no formal releases have been made and only the latest version can be
+ downloaded. </p>
+ <p> The easy way to start is the <a
+ href="installs/crawler/target/wamblee-crawler-0.2-SNAPSHOT-kissbin.zip">standalone program
+ binary version</a> or using the <a
+ href="installs/crawler/kissweb/target/wamblee-crawler-kissweb.war">web application</a>. </p>
+ <p> The latest source can be obtained from subversion with the URL
+ <code>https://wamblee.org/svn/public/utils</code>. The subversion repository allows
+ read-only access to anyone. </p>
+ <p> The application was developed and tested on SuSE linux 10.1 with
+ JBoss 4.0.4 application
+ server. An application server or servlet container is only required for the
+ web application. The crawler requires at least a Java Virtual Machine
+ 1.5 or greater to run. </p>
+ </section>
+
+ <section>
+ <title>Configuring the crawler</title>
+
+ <p> The crawler comes with three configuration files: </p>
+ <ul>
+ <li><code>crawler.xml</code>: basic crawler configuration tailored to the KiSS electronic
+ programme guide.</li>
+ <li><code>programs.xml</code>: containing a description of which programs must be recorded
+ and which programs are interesting.</li>
+ <li><code>org.wamblee.crawler.properties</code>: Containing a configuration </li>
+ </ul>
+ <p> For the standalone program, all configuration files are in the <code>conf</code>
+ directory. For the web application, the properties files is located in the
+ <code>WEB-INF/classes</code> directory of the web application, and
+ <code>crawler.xml</code> and <code>programs.xml</code> are located outside of the web
+ application at a location configured in the properties file. </p>
+
+
+ <section>
+ <title>Crawler configuration <code>crawler.xml</code></title>
+
+ <p> First of all, copy the <code>config.xml.example</code> file to <code>config.xml</code>.
+ After that, edit the first entry of that file and replace <code>user</code> and
+ <code>passwd</code> with your personal user id and password for the KiSS Electronic
+ Programme Guide. </p>
+ </section>
+
+ <section>
+ <title>Program configuration</title>
+ <p> Interesting TV shows are described using <code>program</code> elements. Each
+ <code>program</code> element contains one or more <code>match</code> elements that
+ describe a condition that the interesting program must match. </p>
+ <p> Matching can be done on the following properties of a program: </p>
+ <table>
+ <tr>
+ <th>Field name</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>name</td>
+ <td>Program name</td>
+ </tr>
+ <tr>
+ <td>description</td>
+ <td>Program description</td>
+ </tr>
+ <tr>
+ <td>channel</td>
+ <td>Channel name</td>
+ </tr>
+ <tr>
+ <td>keywords</td>
+ <td>Keywords/classification of the program.</td>
+ </tr>
+ </table>
+ <p> The field to match is specified using the <code>field</code> attribute of the
+ <code>match</code> element. If no field name is specified then the program name is
+ matched. Matching is done by converting the field value to lowercase and then doing a
+ perl-like regular expression match of the provided value. As a result, the content of the
+ match element should be specified in lower case otherwise the pattern will never match. If
+ multiple <code>match</code> elements are specified for a given <code>program</code>
+ element, then all matches must apply for a program to be interesting. </p>
+ <p> Example patterns: </p>
+ <table>
+ <tr>
+ <th>Pattern</th>
+ <th>Examples of matching field values</th>
+ </tr>
+ <tr>
+ <td>the.*x.*files</td>
+ <td>"The X files", "The X-Files: the making of"</td>
+ </tr>
+ <tr>
+ <td>star trek</td>
+ <td>"Star Trek Voyager", "Star Trek: The next generation"</td>
+ </tr>
+ </table>
+
+ <p> It is possible that different programs cannot be recorded since they overlap. To deal
+ with such conflicts, it is possible to specify a priority using the <code>priority</code>
+ element. Higher values of the priority value mean a higher priority. If two programs have
+ the same priority, then it is (more or less) unspecified which of the two will be
+ recorded, but it will at least record one program. If no priority is specified, then the
+ priority is 1 (one). </p>
+
+ <p> Since it is not always desirable to try to record every program that matches the
+ criteria, it is also possible to generate notifications for interesting programs only
+ without recording them. This is done by specifying the <code>action</code> alement with
+ the content <code>notify</code>. By default, the <code>action</code> is
+ <code>record</code>. To make the mail reports more readable it is possible to also assign
+ a category to a program for grouping interesting programs. This can be done using the
+ <code>category</code> element. Note that if the <code>action</code> is
+ <code>notify</code>. then the <code>priority</code> element is not used. </p>
+
+ </section>
+
+ <section>
+ <title>Notification configuration</title>
+ <p> Edit the configuration file <code>org.wamblee.crawler.properties</code>. The properties
+ file is self-explanatory. </p>
+ </section>
+ </section>
+
+
+
+
+ <section>
+ <title>Installing and running the crawler</title>
+
+ <section>
+ <title>Standalone application</title>
+ <p> In the binary distribution, execute the <code>run</code> script for your operating
+ system (<code>run.bat</code> for windows, and <code>run.sh</code> for unix). </p>
+ </section>
+
+ <section>
+ <title>Web application</title>
+ <p> After deploying the web application, navigate to the application in your browser (e.g.
+ <code>http://localhost:8080/wamblee-crawler-kissweb</code>). The screen should show an
+ overview of the last time it ran (if it ran before) as well as a button to run the crawler
+ immediately. Also, the result of the last run can be viewed. The crawler will run
+ automatically starting after 19:00,
+ and will retry at 1 hour intervals in case
+ of failure to retrieve programme information.
+ </p>
+
+ <p>
+ Since the crawler checks the status at
+ 1 hour intervals it can run for the first time anytime between 19:00 and 20:00. This is done
+ on purpose since it means that crawlers run by different people will not all start running
+ simultaneously and is thus more friendly to the KiSS servers. </p>
+ </section>
+
+ <section>
+ <title>Source distribution</title>
+ <p> With the source code, build everything with maven2 as follows:</p>
+ <source>
+ mvn -Dmaven.test.skip=true install
+ cd crawler
+ mvn package assembly:assembly
+ </source>
+ <p>
+ After this, locate the
+ binary distribution in the <code>target</code> subdirectory of the <code>crawler</code>
+ directory. Then
+ proceed as for the binary distribution.</p>
+
+ </section>
+
+ <section>
+ <title>General usage</title>
+ <p> When the crawler runs, it retrieves the programs for tomorrow.
+ </p>
+ <note> If you deploy the web application today, it will run automatically on the next (!)
+ day. This even holds if you deploy the application before the normal scheduled time. </note>
+ </section>
+
+
+ </section>
+
+ <section id="examples">
+ <title>Examples</title>
+
+ <p> The best example is in the distribution itself. It is my personal
+ <code>programs.xml</code> file. </p>
+ </section>
+
+ <section>
+ <title>Contributing</title>
+
+ <p> You are always welcome to contribute. If you find a problem just tell me about it and if
+ you have ideas am I always interested to hear about them. </p>
+ <p> If you are a programmer and have a fix for a bug, just send me a patch and if you are
+ fanatic enough and have ideas, I can also give you write access to the repository. </p>
+ </section>
+
+
+ </body>
+</document>
--- /dev/null
+
+
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>KiSS Crawler overview page</title>
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="expires" content="0">
+
+ <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
+ <meta http-equiv="description" content="This is my page">
+
+ <!--
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ -->
+ </head>
+
+ <body>
+ <h1>KiSS Crawler Overview</h1>
+
+ <TABLE border="1">
+ <tr>
+
+ <td>
+ Currently running:
+ </td>
+ <td>
+ false
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ Last executed at:
+ </td>
+
+ <td>
+ Sat May 06 05:18:54 CEST 2006
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last result:
+ </td>
+ <td>
+ true
+ </td>
+
+ </tr>
+ <tr>
+ <td>
+ Last message:
+ </td>
+ <td>
+
+ </td>
+ </tr>
+ <tr>
+
+ <td>
+ Last report:
+ </td>
+ <td>
+ <a href="details.html">details</a>
+ </td>
+ </tr>
+
+ </TABLE>
+
+ <FORM action="runnow">
+
+ <INPUT type="submit" name="runnow" value="Run Crawler as soon as possible">
+ </FORM>
+
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Ascii Art sample</title>
+ </header>
+ <body>
+ <section>
+ <title>Sample Ascii Art</title>
+ <p>To create a <code>.png</code> image like the one below with ASCII art, just save
+ the text file with the <code>.aart</code> extension and then link from any page
+ as an image (<code><image src="asci-art-file.png"/></code>).</p>
+ <p><img src="cocoon-pyramid.png" alt="cocoon pyramid of management-(logic-content-style)"/></p>
+ <p>Here is the source file that has created the above image.</p>
+ <source>
+
+ +-------------------+
+ | Management |
+ +-+-------+-------+-+
+ | | |
+ | | |
+ +-------+ +----+----+ +-------+
+ | logic +--+ content +--+ style |
+ +-------+ +---------+ +-------+
+
+ </source>
+ <p>An ascii art pad recognized following ascii characters:</p>
+ <ul>
+ <li> '-' horizontal SVG line</li>
+ <li>'|' vertical SVG line</li>
+ <li> '+' corner</li>
+ <li> \ oblique line</li>
+ <li> String starting with letter, digit, or '_' is converted to a SVG text.</li>
+ </ul>
+ </section>
+ </body>
+ <footer>
+ <legal>Copyright 2002-2004 The Apache Software Foundation or its licensors, as applicable.</legal>
+ </footer>
+</document>
--- /dev/null
+
+ +-------------------+
+ | Management |
+ +-+-------+-------+-+
+ | | |
+ | | |
+ +-------+ +----+----+ +-------+
+ | logic +--+ content +--+ style |
+ +-------+ +---------+ +-------+
+
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE greeting PUBLIC "-//Acme//DTD Hello Document V1.0//EN" "hello-v10.dtd">
+<greeting>
+Hello XML Custom World!!
+</greeting>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Custom Schema</title>
+ </header>
+ <body>
+ <p>Forrest comes with a set of schemas for common documents, however, if you have existing documents
+ that use a different schema you will want to tell Forrest how to work with them. The best way of doing
+ this is to <a href="http://forrest.apache.org/0.7/docs/howto/howto-buildPlugin.html">build a plugin</a>
+ so that you can easily reuse the functionality on different projects. Plugins also allow you to share
+ this new functionality with other users, and to benefit from their contributions to your work.</p>
+
+ <p>If you don't want to build a plugin you can make Forrest process them within your project sitemap
+ (but this won't really save you any work since the process is almost the same). This sample site has
+ a demonstration of using a custom DTD. If you request <a href="custom.html"><a href="custom.html"></a>
+ you can see the results. Take a look at the project <code>sitemap.xmap</code> to see how it is done.</p>
+
+ <note>Adding custom schemas with a plugin has the added benefit of being able to add the schema
+ definition to the catalog file rather than having to reference it directly from within the XML
+ document.</note>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.3//EN" "http://forrest.apache.org/dtd/document-v13.dtd">
+<document>
+ <header>
+ <title>The Apache Forrest xdocs document-v1.3 DTD</title>
+ <notice>The content of this document doesn't make any sense at all.</notice>
+ <abstract>This is a demonstration document using all possible elements in
+ the current Apache Forrest xdocs <code>document-v13.dtd</code>
+ </abstract>
+ </header>
+ <body>
+ <note>
+ This is a demonstration document using all possible elements in the
+ current Apache Forrest xdocs <code>document-v13.dtd</code>
+ (See the <link href="#changes">DTD changes</link> section at the bottom.)
+ </note>
+ <section id="sample">
+ <title>Sample Content</title>
+ <p><strong>Hint:</strong> See the xml source to see how the various
+ elements are used and see the
+<!-- FOR-321 workaround
+ <link href="ext:dtd-docs">DTD reference documentation</link>.
+-->
+ <link href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</link>.
+ </p>
+ <section id="block-inline">
+ <title>Block and inline elements</title>
+ <p>This is a simple paragraph. Most documents contain a fair amount of
+ paragraphs. Paragraphs are called <code><p></code>.</p>
+ <p xml:space="preserve"
+ >With the <code><p xml:space="preserve"></code> attribute, you can declare
+ that whitespace should be preserved, without implying it is in any other
+ way special.</p>
+ <p>
+ This next paragraph has a class attribute of 'quote'. CSS can
+ be used to present this <code><p class='quote'></code> in
+ a different style than the other paragraphs. The handling of
+ this quoted paragraph is defined in the <extra-css>
+ element in the skinconf.xml.
+ </p>
+ <p class="quote">
+ Anyway, like I was sayin', shrimp is the fruit of the sea. You can
+ barbecue it, boil it, broil it, bake it, sautee it. Dey's uh,
+ shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried,
+ stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp,
+ pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and
+ potatoes, shrimp burger, shrimp sandwich. That- that's about it.
+ </p>
+ <p>A number of in-line elements are available in the DTD, we will show them
+ inside an unordered list (<code><ul></code>):</p>
+ <ul>
+ <li>Here is a simple list item (<code><li></code>).</li>
+ <li>Have you seen the use of the <code><code></code> element in the
+ previous item?</li>
+ <li>Also, we have <code><sub></code> and <code><sup></code>
+ elements to show content <sup>above</sup> or <sub>below</sub> the text
+ baseline.</li>
+ <li>There is a facility to <em>emphasize</em> certain words using the
+ <code><em></code> <strong><code><strong></code></strong>
+ elements.</li>
+ <li>We can use
+ <icon height="22" width="26" src="../images/icon.png" alt="feather"/>
+ <code><icon></code>s too.</li>
+ <li>Another possibility is the <code><img></code> element:
+ <img src="../images/icon.png" alt="another feather" height="22" width="26"/>,
+ which offers the ability to refer to an image map.</li>
+ <li>We have elements for hyperlinking:
+ <dl>
+ <dt><code><link href="faq.html"></code></dt>
+ <dd>Use this to
+ <link href="faq.html" title="Example of a document via link">link</link>
+ to another document. As per normal, this will open the new document
+ in the same browser window.</dd>
+
+ <dt><code><link href="#section"></code></dt>
+ <dd>Use this to
+ <link href="#section" title="Example of a document via local anchor">link</link>
+ to the named anchor in the current document.
+ </dd>
+
+ <dt><code><link href="faq.html#forrest"></code></dt>
+ <dd>Use this to
+ <link href="faq.html#forrest" title="Example of a document via link and anchor">link</link>
+ to another document and go to the named anchor. This will open
+ the new document in the same browser window.
+ </dd>
+
+ <dt><code><jump href="faq.html"></code></dt>
+ <dd>Use this to
+ <jump href="faq.html" title="Example of a document via jump">jump</jump>
+ to another document and optionally go to a named
+ <jump href="faq.html#forrest" title="Example of a document via jump to anchor">anchor</jump>
+ within that document. This will open the new document in the same
+ browser window. So what is the difference between link and jump?
+ The jump behaves differently, in that it will replace any frames
+ in the current window.
+ This is the equivalent of
+ <code><a ... target="_top"></code>
+ </dd>
+
+ <dt><code><fork href="faq.html"></code></dt>
+ <dd>Use this to
+ <fork href="faq.html" title="Example of a document via fork">fork</fork>
+ your webbrowser to another document. This will open the document
+ in a new, unnamed browser window.
+ This is the equivalent of
+ <code><a ... target="_blank"></code>
+ </dd>
+ </dl></li>
+
+ <li>Oh, by the way, a definition list <code><dl></code> was used inside
+ the previous list item. We could put another
+ <ul>
+ <li>unordered list</li>
+ <li>inside the list item</li>
+ </ul>
+ <table>
+ <caption>A sample nested table</caption>
+ <tr><td>Or even tables.. </td><td>
+ <table><tr><td>inside tables..</td></tr></table>
+ </td></tr>
+ <tr><td>or inside lists, but I believe this liberty gets quickly quite
+ hairy as you see.</td></tr>
+ </table>
+ </li>
+ </ul>
+ <p>So far for the in-line elements, let's look at some paragraph-level
+ elements.</p>
+ <fixme author="SN">The <code><fixme></code> element is used for stuff
+ which still needs work. Mind the <code>author</code> attribute!</fixme>
+ <note>Use the <code><note></code> element to draw attention to something, e.g. ...The <code><code></code> element is used when the author can't
+ express himself clearly using normal sentences ;-)</note>
+ <warning>Sleep deprivation can be the result of being involved in an open
+ source project. (a.k.a. the <code><warning></code> element).
+ </warning>
+ <note label="Important">If you want your own labels for notes and
+ warnings, specify them using the <code>label</code> attribute.
+ </note>
+ <p>Apart from unordered lists, we have ordered lists too, of course.</p>
+ <ol>
+ <li>Item 1</li>
+ <li>Item 2</li>
+ <li>This should be 3 if my math is still OK.</li>
+ </ol>
+ </section>
+
+ <section id="presentations">
+ <title>Various presentation formats</title>
+
+ <p>This sample document, written in document-v13 XML can be presented
+ via Forrest in a number of different formats. The links in the
+ following list show this document in each of the currently available
+ formats.</p>
+
+ <p>Each of the formats can be made available as a link near the top of
+ the page. Actual placement of those links depends on the skin
+ currently in use. Those links are enabled in the skinconf.xml via the
+ <disable-XXX-link> elements in the skinconf.xml</p>
+
+ <table>
+ <tr>
+ <th>Presentation Format</th>
+
+ <th>Description</th>
+
+ <th>skinconf.xml Element</th>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.html">HTML</link></td>
+
+ <td>This document in HTML format. </td>
+
+ <td>Always generated by default. Cannot be turned off.</td>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.xml">XML</link></td>
+
+ <td>This document in its raw XML format.</td>
+
+ <td><disable-xml-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td><link href="document-v13.pdf">PDF</link></td>
+
+ <td>This document as Adobe PDF</td>
+
+ <td><disable-pdf-link>. By default, set to false, meaning
+ that this link will be shown.</td>
+ </tr>
+
+ <tr>
+ <td>Text</td>
+
+ <td><p>This document as straight text.</p>
+ <p>For additional information see the Forrest text-output
+ plugin.</p></td>
+
+ <td><disable-txt-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td>POD</td>
+
+ <td><p>This document as Perl POD (Plain Old Documentation). Text
+ with minimal formatting directives. If on a *nix system with perl
+ installed, see "man perlpod".</p>
+ <p>For additional information see the Forrest pod-output
+ plugin.</p></td>
+
+ <td><disable-pod-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+ </table>
+ </section>
+ <section id="section">
+ <title>Using sections</title>
+ <p>You can use sections to put some structure in your document. For some
+ strange historical reason, the section title is an attribute of the
+ <code><section></code> element.</p>
+ </section>
+ <section id="sub-section">
+ <title>Sections, the sequel</title>
+ <p>Just some second section.</p>
+ <section id="sub-sub-section">
+ <title>Section 2.1</title>
+ <p>Which contains a subsection (2.1).</p>
+ </section>
+ </section>
+
+ <section id="source">
+ <title>Showing preformatted source code</title>
+ <p>Enough about these sections. Let's have a look at more interesting
+ elements, <code><source></code> for instance:</p>
+ <source>
+// This example is from the book _Java in a Nutshell_ by David Flanagan.
+// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates.
+// You may study, use, modify, and distribute this example for any purpose.
+// This example is provided WITHOUT WARRANTY either expressed or implied.
+
+import java.applet.*; // Don't forget these import statements!
+import java.awt.*;
+
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ // The Graphics class is how you do all drawing in Java.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}</source>
+ <p>CDATA sections are used within
+ <code><source></code> elements so that you can write pointy
+ brackets without needing to escape them with messy
+ <code>&lt;</code> entities ...
+ </p>
+ <source><![CDATA[
+<pointy>
+ easy
+</pointy>
+]]></source>
+ <p>Please take care to still use a sensible line-length within your
+ source elements.</p>
+ </section>
+
+ <section id="table">
+ <title>Using tables</title>
+ <p>And now for a table:</p>
+ <table>
+ <caption>Table caption</caption>
+ <tr>
+ <th>heading cell 1</th>
+ <th>heading cell 2</th>
+ <th>heading cell 3</th>
+ </tr>
+ <tr>
+ <td>data cell</td>
+ <td colspan="2">this data cell spans two columns</td>
+ </tr>
+ <tr>
+ <td>
+ Tables can be nested:
+ </td>
+ <td>
+ <table>
+ <tr>
+ <th>column 1</th>
+ <th>column 2</th>
+ </tr>
+ <tr>
+ <td>cell A</td>
+ <td>cell B</td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <ul><li>and can include most other elements</li><li>such as lists</li></ul>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ <anchor id="second-figure-anchor"/>
+ <section id="figure">
+ <title>Using figures</title>
+ <p>And a <code><figure></code> to end all of this.
+ Note that this can also be implemented with an
+ <code><img></code> element.
+ </p>
+ <figure src="../images/project.png" alt="The fine Forrest logo" width="220" height="65"/>
+ </section>
+ </section>
+
+ <section id="changes">
+ <title>DTD changes</title>
+ <p>See the generated
+<!-- FOR-321 workaround
+ <link href="ext:dtd-docs">DTD reference documentation</link>.
+-->
+ <link href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</link>.
+ </p>
+ <section id="changes-13">
+ <title>Changes since document-v12</title>
+ <p>
+ All v1.2 docs will work fine as v1.3 DTD. The main change is the
+ addition of a @class attribute to every element, which enables the
+ "extra-css" section in the skinconf to be put to good use.
+ </p>
+ </section>
+ <section id="changes-12">
+ <title>Changes since document-v11</title>
+ <p>
+ doc-v12 enhances doc-v11 by relaxing various restrictions that were
+ found to be unnecessary.
+ </p>
+ <ul>
+ <li>
+ Links ((link|jump|fork) and inline elements (br|img|icon|acronym) are
+ allowed inside title.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), table and figure|anchor are
+ allowed inside li.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), lists (ol|ul|dl), table,
+ figure|anchor are allowed inside definition lists (dd) and tables (td
+ and dh).
+ </li>
+ <li>
+ Inline content
+ (strong|em|code|sub|sup|br|img|icon|acronym|link|jump|fork) is
+ allowed in strong and em.
+ </li>
+ </ul>
+ </section>
+ </section>
+ </body>
+ <footer>
+ <legal>This is a legal notice, so it is <strong>important</strong>.</legal>
+ </footer>
+</document>
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<html>
+<head>
+ <title>Embedded HTML demonstration page</title>
+</head>
+<body>
+
+<h1><a name="intro" />Embedded HTML demonstration page</h1>
+
+<p>An HTML document is used as the source for this page, and translated
+to the intermediate Apache Forrest xdocs document structure. The sitemap then
+does the normal aggregation with the navigation content and application of
+the skin.
+</p>
+
+<p>
+The html is being interpreted by Forrest and transformed to the
+intermediate Apache xdocs document structure. That stylesheet cannot deal
+with every possibility in unstructured html, so it tries to guess how to
+build <section> elements and such.
+It needs <h1> (<h2> etc.) headings in the source html
+in order to identify sections. Patches are welcome to enhance
+that transformer.
+</p>
+
+<p>
+You can still take advantage of Forrest's
+<a href="http://forrest.apache.org/docs/linking.html">"<b>site:<b>"
+method of linking</a>, for example:
+<a href="site:index"><a href="site:index"></a>
+</p>
+
+<hr>
+<note>XHTML can also be used, but it is just treated as interpreted
+html. Future versions of Forrest will take much more advantage of XHTML.
+</note>
+<hr>
+
+<h1><a name="examples" />Some example uses of HTML</h1>
+<p>
+There are situations when the Apache Forrest xdocs DTD is not sufficient.
+The use of embedded HTML enables you to use HTML code in these situations.
+</p>
+
+<h2><a name="js" />Embedded applets and Javascript</h2>
+
+<p>
+See the
+<a href="javascript:alert('Opened with Javascript via the body of the source html.')">Javascript alert pop-up</a>
+</p>
+
+<h2><a name="forms" />HTML forms for user interaction</h2>
+<p>
+Search the Forrest website via Google:
+<!-- Search Google -->
+<form target="_blank" action="http://www.google.com/search" method="get">
+<input value="forrest.apache.org" name="as_sitesearch" type="hidden">
+<input type=hidden name=ie value=UTF-8>
+<input type=hidden name=oe value=UTF-8>
+<a href="http://www.google.com/">
+<img src="http://www.google.com/logos/Logo_40wht.gif"
+border="0" alt="Google Search" align="middle" width="150" height="55"></a>
+<input type="text" name="as_q" size="25" maxlength="255" value="HTML">
+
+<input type="submit" name="btnG" value="Google Search">
+</form>
+<!-- Search Google -->
+</p>
+
+<p>
+See a demonstration of "html" and "html forms" with our
+<a href="http://forrest.apache.org/mirrors.cgi">Forrest download mirror</a>
+facility and the
+<a href="http://forrest.apache.org/howto/howto-asf-mirror.html">explanation</a> howto document.
+</p>
+
+<h2><a name="invalid" />Invalid HTML</h2>
+<p>
+This paragraph has a missing closing tag for the <p> element. If you look
+at the <a href="embedded_html.xml">XML created by Forrest</a> you'll notice that
+Forrest has fixed this.
+
+<h2>Potentially Invalid XDocs</h2>
+
+<warning>However, it should also be noted that the resultant XML is not a valid document
+since it contains the additional HTML elements. If you are intending to use
+the intermediate XDocs for any purpose be aware of this fact.</warning>
+
+<h2><a name="blink" />Other non-standard html-type abilities</h2>
+<p>
+Use other HTML <blink>delights (???) and tricks</blink>.
+</p>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE faqs PUBLIC "-//APACHE//DTD FAQ V2.0//EN" "http://forrest.apache.org/dtd/faq-v20.dtd">
+
+<faqs>
+ <title>Frequently Asked Questions</title>
+
+ <faqsection id="docs">
+ <title>Documentation</title>
+ <faq id="forrest">
+ <question>
+ How can I help write documentation?
+ </question>
+ <answer>
+ <p>
+ This project uses <a href="ext:forrest">Apache Forrest</a> to
+ generate documentation from XML. Please download a copy of Forrest,
+ which can be used to <a
+ href="ext:forrest/validation">validate</a>, <a
+ href="ext:forrest/webapp">develop</a> and render a project site.
+ </p>
+ </answer>
+ </faq>
+ <!-- More faqs or parts here -->
+ </faqsection>
+ <!-- More faqs or parts here -->
+</faqs>
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!-- ===================================================================
+
+ Apache Hello Document DTD (Version 1.1)
+
+PURPOSE:
+ This DTD was developed to create a sample of custom document.
+
+TYPICAL INVOCATION:
+
+ <!DOCTYPE greeting PUBLIC
+ "-//APACHE//DTD Hello Document Vx.y//EN"
+ "hello-vxy.dtd">
+
+ where
+
+ x := major version
+ y := minor version
+
+NOTES:
+
+FIXME:
+
+CHANGE HISTORY:
+[Version 1.0]
+ 20050112 Initial version. (JJP)
+
+==================================================================== -->
+
+<!ELEMENT greeting (#PCDATA)>
+
+<!-- =============================================================== -->
+<!-- End of DTD -->
+<!-- =============================================================== -->
--- /dev/null
+%PDF-1.3
+%ª«¬
+4 0 obj
+<< /Type /Info
+/Producer (FOP 0.20.4) >>
+endobj
+5 0 obj
+<< /Length 203 /Filter [ /ASCII85Decode /FlateDecode ]
+ >>
+stream
+Gar'!]afWZ&;9q-MRA)RFnblL2&]tQSZsjOOT[ck2SQkp(bfQ[R7ZPq=U24c0dqq_i?B[A.0s\)5f5<IA'lb0eeo`C+`q\Ip/Tke*)7%T+.hT8:QQidXoPLKZM,RXY"bP+;E@%,ZX;V'Aq+M9rH"!g=N5TToDMoqMeUiEe).I_W3q80:jF+;'9bVIeBRb]DhE9:E2be2~>
+endstream
+endobj
+6 0 obj
+<< /Type /Page
+/Parent 1 0 R
+/MediaBox [ 0 0 595 842 ]
+/Resources 3 0 R
+/Contents 5 0 R
+>>
+endobj
+7 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F1
+/BaseFont /Helvetica
+/Encoding /WinAnsiEncoding >>
+endobj
+8 0 obj
+<< /Type /Font
+/Subtype /Type1
+/Name /F5
+/BaseFont /Times-Roman
+/Encoding /WinAnsiEncoding >>
+endobj
+1 0 obj
+<< /Type /Pages
+/Count 1
+/Kids [6 0 R ] >>
+endobj
+2 0 obj
+<< /Type /Catalog
+/Pages 1 0 R
+ >>
+endobj
+3 0 obj
+<<
+/Font << /F1 7 0 R /F5 8 0 R >>
+/ProcSet [ /PDF /ImageC /Text ] >>
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000687 00000 n
+0000000745 00000 n
+0000000795 00000 n
+0000000015 00000 n
+0000000071 00000 n
+0000000365 00000 n
+0000000471 00000 n
+0000000578 00000 n
+trailer
+<<
+/Size 9
+/Root 2 0 R
+/Info 4 0 R
+>>
+startxref
+883
+%%EOF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Samples</title>
+ </header>
+ <body>
+ <section id="please-contribute">
+ <title>If something goes wrong..</title>
+ <p>Patches are welcome: <a href="http://forrest.apache.org/docs/faq.html">Forrest FAQ</a></p>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://apache.org/forrest/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Demonstration of linking</title>
+ </header>
+
+ <body>
+ <section id="overview">
+ <title>Overview</title>
+ <p>Forrest has many powerful techniques for linking between documents
+ and for managing the site navigation. This document demonstrates those
+ techniques.
+ The document "<a href="ext:linking">Menus and Linking</a>"
+ has the full details.
+ </p>
+ </section>
+
+ <section id="uri-space">
+ <title>Building and maintaining consistent URI space</title>
+ <p>
+ When Forrest builds your site, it starts from the front page. Like
+ a robot, it traverses all of the links that it finds in the documents
+ and builds the corresponding pages. Any new links are further traversed.
+ </p>
+ <p>
+ Sometimes those links lead to documents that are generated directly
+ from xml source files, sometimes they are generated from other source
+ via an intermediate xml format. Other times the links lead to raw
+ un-processed content.
+ </p>
+ <p>
+ The site navigation configuration file "<code>site.xml</code>" provides
+ a way to manage this URI space. In the future, when documents are
+ re-arranged and renamed, the site.xml configuration will enable this
+ smoothly.
+ </p>
+ </section>
+
+ <section id="resource-space">
+ <title>Mapping the local resource space to the final URI space</title>
+ <p>
+ For both generated and raw (un-processed) files, the top-level of the
+ URI space corresponds to the "<code>content/xdocs/</code>" directory,
+ i.e. the location of the "<code>site.xml</code>" configuration file.
+ </p>
+ <note>
+ In versions prior to 0.7 raw un-processed content was stored in
+ the "<code>content/</code>" directory. In 0.7 onwards, raw
+ un-processed data is stored alongside the xdocs. In addition,
+ in 0.6 and earlier, HTML documents could be stored in the xdocs
+ directory and served without processing. If you
+ you wish to emulate the behaviour of 0.6 and earlier see the
+ next section.
+ </note>
+ <p>
+ A diagram will help.
+ </p>
+ <source><![CDATA[
+The resource space ==============> The final URI space
+------------------ -------------------
+Generated content ...
+ content/xdocs/index.xml index.html
+ content/xdocs/samples/index.xml samples/index.html
+ content/xdocs/samples/faq.xml samples/faq.html
+ content/xdocs/test1.html test1.html
+ content/xdocs/samples/test3.html samples/test3.html
+ content/xdocs/samples/subdir/test4.html samples/subdir/test4.html
+
+Raw un-processed content ...
+ content/xdocs/hello.pdf hello.pdf
+ content/xdocs/hello.sxw hello.sxw
+ content/xdocs/subdir/hello.sxw subdir/hello.sxw
+]]></source>
+
+ <section>
+ <title>How Plugins May Affect The URI Space</title>
+ <p>By using <a href="site:plugins">Forrest Input Plugins</a>
+ you can process some file formats, such as
+ OpenOffice.org documents and produce processed content from them. For example,
+ the file <code>content/xdocs/hello.sxw</code> can be used to produce a
+ skinned version of the document at with the name <code>hello.html</code>.
+ Similarly, you can use <a href="site:plugins">Forrest Output
+ Plugins</a> to create different output formats such as PDF, in this
+ case <code>content/xdocs/hello.sxw</code> can produce
+ <code>hello.pdf</code>.</p>
+
+ <p>However, this does not affect the handling of raw content. That is, you
+ can still retrieve the raw un-processed version with, for example,
+ <code>hello.sxw</code>. If you want to prevent the user retrieving the
+ un-processed version you will have to create matchers that intercept
+ these requests within your project sitemap.</p>
+ </section>
+
+ </section>
+
+ <section id="generated">
+ <title>Basic link to internal generated pages</title>
+ <p>
+ When this type of link is encountered, Forrest will look for a
+ corresponding xml file, relative to this document (i.e. in
+ <code>content/xdocs/samples/</code>).
+ </p>
+ <p>A generated document in the current directory, which corresponds to
+ <code>content/xdocs/samples/sample.html</code> ...
+ </p>
+ <source><![CDATA[<a href="sample.html">]]><a href="sample.html">sample.html</a><![CDATA[</a>]]></source>
+ <p>In a sub-directory, which corresponds to
+ <code>content/xdocs/samples/subdir/index.html</code> ...
+ </p>
+ <source><![CDATA[<a href="subdir/index.html">]]><a href="subdir/index.html">subdir/index.html</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="raw">
+ <title>Basic link to raw un-processed content</title>
+ <p>
+ Raw content files are not intended for any processing, they are just
+ linked to (e.g. pre-prepared PDFs, zip archives).
+ These files are placed alongside your normal content in the
+ "<code>content/xdocs</code>" directory.
+ </p>
+ <p>A raw document in the current directory, which corresponds to
+ <code>content/xdocs/samples/helloAgain.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="helloAgain.pdf">]]><a href="helloAgain.pdf">helloAgain.pdf</a><![CDATA[</a>]]></source>
+ <p>A raw document in a sub-directory, which corresponds to
+ <code>content/xdocs/samples/subdir/hello.zip</code> ...
+ </p>
+ <source><![CDATA[<a href="subdir/hello.zip">]]><a href="subdir/hello.zip">subdir/hello.zip</a><![CDATA[</a>]]></source>
+ <p>A raw document at the next level up, which corresponds to
+ <code>content/hello.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="../hello.pdf">]]><a href="../hello.pdf">../hello.pdf</a><![CDATA[</a>]]></source>
+
+ <section>
+ <title>Serving (X)HTML content without Skinning</title>
+
+ <p>Prior to version 0.7, the raw un-processed content was stored in
+ the "<code>content/</code>" directory. In 0.7 onwards, raw
+ un-processed data is stored alongside the xdocs. In addition
+ in 0.6 and earlier, HTML files could be stored in the xdocs
+ directory and they would be served without further processing.
+ As described above, this is not the case in 0.7 where HTML files
+ are, by default, skinned by Forrest.</p>
+
+ <p>If you
+ you wish to emulate the behaviour of 0.6 and earlier then you
+ must add the following to your project sitemap.</p>
+
+ <source>
+<map:match pattern="**.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{0}">
+ <map:read src="{project:content}/{0}" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ <map:when test="{project:content.xdocs}{0}">
+ <map:read src="{project:content.xdocs}/{0}" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content.xdocs}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ </map:select>
+</map:match>
+ </source>
+
+ <p>The above allows us to create links to un-processed skinned files stored
+ in the <code>{project:content}</code> or <code>{project:content.xdocs}</code>
+ directory. For example:
+ <a href="/test1.html">HTML content</a>. However, it will
+ break the 0.7 behaviour of skinning HTML content. For this reason the old
+ ".ehtml" extension can be used to embed HTML content in a Forrest skinned
+ site </p>
+
+ <p>Note that you can change the matchers above to selectively serve some
+ content as raw un-processed content, whilst still serving other content
+ as skinned documents. For example, the following snippet would allow
+ you to serve the content of an old, deprecated site without processing
+ from Forrest, whilst still allowing all other content to be processed
+ by Forrest in the normal way:</p>
+
+ <source>
+<map:match pattern="old_site/**.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{1}.html">
+ <map:read src="{project:content}/{1}.html" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+</map:match>
+ </source>
+
+ <p>For example, <a href="/old_site/test1.html">HTML content</a>.</p>
+ </section>
+ </section>
+
+ <section id="url">
+ <title>Full URL to external documents</title>
+ <p>A full URL ...</p>
+ <source><![CDATA[<a href="http://forrest.apache.org/">]]><a href="http://forrest.apache.org/">http://forrest.apache.org/</a><![CDATA[</a>]]></source>
+ <p>A full URL with a fragment identifier ...</p>
+ <source><![CDATA[<a href="http://forrest.apache.org/faq.html#link_raw">]]><a href="http://forrest.apache.org/faq.html#link_raw">http://forrest.apache.org/faq.html#link_raw</a><![CDATA[</a>]]></source>
+ <p>
+ Note that Forrest does not traverse external links to look for
+ other links.
+ </p>
+ </section>
+
+ <section id="site">
+ <title>Using site.xml to manage the links</title>
+ <p>As you will have discovered, using pathnames with ../../ etc. will
+ get very nasty. Real problems occur when you use a smart text editor
+ that tries to manage the links for you. For example, it will have
+ trouble linking to the raw content files which are not yet in their
+ final location.
+ </p>
+ <p>
+ Links and filenames are bound to change and re-arrange. It is
+ essential to only change those links in one central place, not in every
+ document.
+ </p>
+ <p>
+ The "<code>site.xml</code>" configuration file to the rescue. It maps
+ symbolic names to actual resources.
+ </p>
+
+ <section id="site-simple">
+ <title>Basic link to internal generated pages</title>
+ <p>This single entry ...</p>
+ <source><![CDATA[<index label="Index" href="index.html"/>]]></source>
+ <p>
+ enables a simple link to a generated document, which corresponds to
+ <code>content/xdocs/index.xml</code> ...
+ </p>
+ <source><![CDATA[<a href="site:index">]]><a href="site:index">site:index</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="site-compound">
+ <title>Group some items</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <samples label="Samples" href="samples/" tab="samples">
+ <faq label="FAQ" href="faq.html"/>
+ ...
+ </samples>
+]]></source>
+ <p>
+ enables a link to a generated document, which corresponds to
+ <code>content/xdocs/samples/index.xml</code> ...
+ </p>
+ <source><![CDATA[<a href="site:samples">]]><a href="site:samples">site:samples</a><![CDATA[</a>]]></source>
+ <p>
+ and a link to a generated document, which corresponds to
+ <code>content/xdocs/samples/faq.xml</code> ...
+ </p>
+ <source>
+<![CDATA[<a href="site:faq">]]><a href="site:faq">site:faq</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="site:samples/faq">]]><a href="site:samples/faq">site:samples/faq</a><![CDATA[</a>]]>
+ </source>
+ </section>
+
+ <section id="site-fragment">
+ <title>Fragment identifiers</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <samples label="Samples" href="samples/" tab="samples">
+ <sample label="Apache document" href="sample.html">
+ <top href="#top"/>
+ <section href="#section"/>
+ </sample>
+ ...
+ </samples>
+]]></source>
+ <p>
+ enables a link to a fragment identifier within the
+ <code>samples/sample.html</code> document ...
+ </p>
+ <source><![CDATA[<a href="site:samples/sample/section">]]><a href="site:samples/sample/section">site:samples/sample/section</a><![CDATA[</a>]]></source>
+ </section>
+
+ <section id="site-raw">
+ <title>Define items for raw content</title>
+ <p>This entry ...</p>
+ <source><![CDATA[<hello_print href="hello.pdf"/>]]></source>
+ <p>
+ enables a link to a raw document, which corresponds to
+ <code>content/hello.pdf</code> ...
+ </p>
+ <source><![CDATA[<a href="site:hello_print">]]><a href="site:hello_print">site:hello_print</a><![CDATA[</a>]]></source>
+
+ </section>
+
+ <section id="site-ext">
+ <title>External links</title>
+ <p>This compound entry ...</p>
+ <source><![CDATA[
+ <external-refs>
+ <forrest href="http://forrest.apache.org/">
+ <linking href="docs/linking.html"/>
+ <webapp href="docs/your-project.html#webapp"/>
+ </forrest>
+ </external-refs>
+]]></source>
+ <p>
+ enables a link to an external URL ...
+ </p>
+ <source><![CDATA[<a href="ext:forrest">]]><a href="ext:forrest">ext:forrest</a><![CDATA[</a>]]></source>
+ <p>
+ and a link to another external URL ...
+ </p>
+ <source>
+<![CDATA[<a href="ext:linking">]]><a href="ext:linking">ext:linking</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="ext:forrest/linking">]]><a href="ext:forrest/linking">ext:forrest/linking</a><![CDATA[</a>]]>
+ </source>
+ <p>
+ and a link to another external URL with a fragment identifier ...
+ </p>
+ <source>
+<![CDATA[<a href="ext:webapp">]]><a href="ext:webapp">ext:webapp</a><![CDATA[</a>]]>
+which can also be a complete reference
+<![CDATA[<a href="ext:forrest/webapp">]]><a href="ext:forrest/webapp">ext:forrest/webapp</a><![CDATA[</a>]]>
+ </source>
+ </section>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>The Apache Forrest xdocs document-v2.0 DTD</title>
+ <notice>The content of this document doesn't make any sense at all.</notice>
+ <abstract>This is a demonstration document using all possible elements in
+ the current Apache Forrest xdocs <code>document-v20.dtd</code>
+ </abstract>
+ </header>
+ <body>
+ <note>
+ This is a demonstration document using all possible elements in the
+ current Apache Forrest xdocs <code>document-v20.dtd</code>
+ (See the <a href="#changes">DTD changes</a> section at the bottom.)
+ </note>
+ <section id="sample">
+ <title>Sample Content</title>
+ <p><strong>Hint:</strong> See the xml source to see how the various
+ elements are used and see the
+<!-- FOR-321 workaround
+ <a href="ext:dtd-docs">DTD reference documentation</a>.
+-->
+ <a href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</a>.
+ </p>
+ <section id="block-inline">
+ <title>Block and inline elements</title>
+ <p>This is a simple paragraph. Most documents contain a fair amount of
+ paragraphs. Paragraphs are called <code><p></code>.</p>
+ <p xml:space="preserve"
+ >With the <code><p xml:space="preserve"></code> attribute, you can declare
+ that whitespace should be preserved, without implying it is in any other
+ way special.</p>
+ <p>
+ This next paragraph has a class attribute of 'quote'. CSS can
+ be used to present this <code><p class='quote'></code> in
+ a different style than the other paragraphs. The handling of
+ this quoted paragraph is defined in the <extra-css>
+ element in the skinconf.xml.
+ </p>
+ <p class="quote">
+ Anyway, like I was sayin', shrimp is the fruit of the sea. You can
+ barbecue it, boil it, broil it, bake it, sautee it. Dey's uh,
+ shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried,
+ stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp,
+ pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and
+ potatoes, shrimp burger, shrimp sandwich. That- that's about it.
+ </p>
+ <p>A number of in-line elements are available in the DTD, we will show them
+ inside an unordered list (<code><ul></code>):</p>
+ <ul>
+ <li>Here is a simple list item (<code><li></code>).</li>
+ <li>Have you seen the use of the <code><code></code> element in the
+ previous item?</li>
+ <li>Also, we have <code><sub></code> and <code><sup></code>
+ elements to show content <sup>above</sup> or <sub>below</sub> the text
+ baseline.</li>
+ <li>There is a facility to <em>emphasize</em> certain words using the
+ <code><em></code> <strong><code><strong></code></strong>
+ elements.</li>
+ <li>We can use
+ <icon height="22" width="26" src="../images/icon.png" alt="feather"/>
+ <code><icon></code>s too.</li>
+ <li>Another possibility is the <code><img></code> element:
+ <img src="../images/icon.png" alt="another feather" height="22" width="26"/>,
+ which offers the ability to refer to an image map.</li>
+ <li>We have elements for hyperlinking:
+ <dl>
+ <dt><code><a href="faq.html"></code></dt>
+ <dd>Use this to
+ <a href="faq.html" title="Example of a document via link">link</a>
+ to another document. As per normal, this will open the new document
+ in the same browser window.</dd>
+
+ <dt><code><a href="#section"></code></dt>
+ <dd>Use this to
+ <a href="#section" title="Example of a document via local anchor">link</a>
+ to the named anchor in the current document.
+ </dd>
+
+ <dt><code><a href="faq.html#forrest"></code></dt>
+ <dd>Use this to
+ <a href="faq.html#forrest" title="Example of a document via link and anchor">link</a>
+ to another document and go to the named anchor. This will open
+ the new document in the same browser window.
+ </dd>
+ <dt>Targetted window control with jump and fork.</dt>
+ <dd>See demonstration
+ <a href="#link-class">using class attribute on links</a>.
+ </dd>
+ </dl></li>
+
+ <li>Oh, by the way, a definition list <code><dl></code> was used inside
+ the previous list item. We could put another
+ <ul>
+ <li>unordered list</li>
+ <li>inside the list item</li>
+ </ul>
+ <table>
+ <caption>A sample nested table</caption>
+ <tr><td>Or even tables.. </td><td>
+ <table><tr><td>inside tables..</td></tr></table>
+ </td></tr>
+ <tr><td>or inside lists, but I believe this liberty gets quickly quite
+ hairy as you see.</td></tr>
+ </table>
+ </li>
+ </ul>
+ <p>So far for the in-line elements, let's look at some paragraph-level
+ elements.</p>
+ <fixme author="SN">The <code><fixme></code> element is used for stuff
+ which still needs work. Mind the <code>author</code> attribute!</fixme>
+ <note>Use the <code><note></code> element to draw attention to something, e.g. ...The <code><code></code> element is used when the author can't
+ express himself clearly using normal sentences ;-)</note>
+ <warning>Sleep deprivation can be the result of being involved in an open
+ source project. (a.k.a. the <code><warning></code> element).
+ </warning>
+ <note label="Important">If you want your own labels for notes and
+ warnings, specify them using the <code>label</code> attribute.
+ </note>
+ <p>Apart from unordered lists, we have ordered lists too, of course.</p>
+ <ol>
+ <li>Item 1</li>
+ <li>Item 2</li>
+ <li>This should be 3 if my math is still OK.</li>
+ </ol>
+ </section>
+
+ <section id="presentations">
+ <title>Various presentation formats</title>
+
+ <p>This sample document, written in document-v20 XML can be presented
+ via Forrest in a number of different formats. The links in the
+ following list show this document in each of the currently available
+ formats.</p>
+
+ <p>Each of the formats can be made available as a link near the top of
+ the page. Actual placement of those links depends on the skin
+ currently in use. Those links are enabled in the skinconf.xml via the
+ <disable-XXX-link> elements in the skinconf.xml</p>
+
+ <table>
+ <tr>
+ <th>Presentation Format</th>
+
+ <th>Description</th>
+
+ <th>skinconf.xml Element</th>
+ </tr>
+
+ <tr>
+ <td><a href="sample.html">HTML</a></td>
+
+ <td>This document in HTML format. </td>
+
+ <td>Always generated by default. Cannot be turned off.</td>
+ </tr>
+
+ <tr>
+ <td><a href="sample.xml">XML</a></td>
+
+ <td>This document in its raw XML format.</td>
+
+ <td><disable-xml-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td><a href="sample.pdf">PDF</a></td>
+
+ <td>This document as Adobe PDF</td>
+
+ <td><disable-pdf-link>. By default, set to false, meaning
+ that this link will be shown.</td>
+ </tr>
+
+ <tr>
+ <td>Text</td>
+
+ <td><p>This document as straight text.</p>
+ <p>For additional information see the Forrest text-output
+ plugin.</p></td>
+
+ <td><disable-txt-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+
+ <tr>
+ <td>POD</td>
+
+ <td><p>This document as Perl POD (Plain Old Documentation). Text
+ with minimal formatting directives. If on a *nix system with perl
+ installed, see "man perlpod".</p>
+ <p>For additional information see the Forrest pod-output
+ plugin.</p></td>
+
+ <td><disable-pod-link>. By default, set to true, meaning
+ that this link will not be shown.</td>
+ </tr>
+ </table>
+ </section>
+ <section id="section">
+ <title>Using sections</title>
+ <p>You can use sections to put some structure in your document. For some
+ strange historical reason, the section title is an attribute of the
+ <code><section></code> element.</p>
+ </section>
+ <section id="sub-section">
+ <title>Sections, the sequel</title>
+ <p>Just some second section.</p>
+ <section id="sub-sub-section">
+ <title>Section 2.1</title>
+ <p>Which contains a subsection (2.1).</p>
+ </section>
+ </section>
+
+ <section id="source">
+ <title>Showing preformatted source code</title>
+ <p>Enough about these sections. Let's have a look at more interesting
+ elements, <code><source></code> for instance:</p>
+ <source>
+// This example is from the book _Java in a Nutshell_ by David Flanagan.
+// Written by David Flanagan. Copyright (c) 1996 O'Reilly & Associates.
+// You may study, use, modify, and distribute this example for any purpose.
+// This example is provided WITHOUT WARRANTY either expressed or implied.
+
+import java.applet.*; // Don't forget these import statements!
+import java.awt.*;
+
+public class FirstApplet extends Applet {
+ // This method displays the applet.
+ // The Graphics class is how you do all drawing in Java.
+ public void paint(Graphics g) {
+ g.drawString("Hello World", 25, 50);
+ }
+}</source>
+ <p>CDATA sections are used within
+ <code><source></code> elements so that you can write pointy
+ brackets without needing to escape them with messy
+ <code>&lt;</code> entities ...
+ </p>
+ <source><![CDATA[
+<pointy>
+ easy
+</pointy>
+]]></source>
+ <p>Please take care to still use a sensible line-length within your
+ source elements.</p>
+ </section>
+
+ <section id="table">
+ <title>Using tables</title>
+ <p>And now for a table:</p>
+ <table>
+ <caption>Table caption</caption>
+ <tr>
+ <th>heading cell 1</th>
+ <th>heading cell 2</th>
+ <th>heading cell 3</th>
+ </tr>
+ <tr>
+ <td>data cell</td>
+ <td colspan="2">this data cell spans two columns</td>
+ </tr>
+ <tr>
+ <td>
+ Tables can be nested:
+ </td>
+ <td>
+ <table>
+ <tr>
+ <th>column 1</th>
+ <th>column 2</th>
+ </tr>
+ <tr>
+ <td>cell A</td>
+ <td>cell B</td>
+ </tr>
+ </table>
+ </td>
+ <td>
+ <ul><li>and can include most other elements</li><li>such as lists</li></ul>
+ </td>
+ </tr>
+ </table>
+ </section>
+
+ <anchor id="second-figure-anchor"/>
+ <section id="figure">
+ <title>Using figures</title>
+ <p>And a <code><figure></code> to end all of this.
+ Note that this can also be implemented with an
+ <code><img></code> element.
+ </p>
+ <figure src="../images/project.png" alt="The fine Forrest logo" width="220" height="65"/>
+ </section>
+ <section id="link-class">
+ <title>Using class attribute on links</title>
+
+ <p>The document-v13 had elements <fork> and <jump>. In
+ document-v20, those elements no longer exist but the functionality can
+ be duplicated by using the @class attribute.
+ Even though the opening of separate windows should be under the
+ control of the user, these techniques can still be employed.</p>
+
+ <table>
+ <tr>
+ <th><p>Document V1.3</p></th>
+
+ <th><p>Document V2.0</p></th>
+ </tr>
+
+ <tr>
+ <td><p><fork href="faq.html"></p></td>
+
+ <td><a class="fork" href="faq.html"><a class="fork"
+ href="faq.html"></a></td>
+ </tr>
+
+ <tr>
+ <td><p><jump href="faq.html"></p></td>
+
+ <td><p><a class="jump" href="faq.html"><a class="jump"
+ href="faq.html"></a></p></td>
+ </tr>
+ </table>
+ </section>
+ </section>
+
+ <section id="changes">
+ <title>DTD changes</title>
+ <p>See the generated
+<!-- FOR-321 workaround
+ <a href="ext:dtd-docs">DTD reference documentation</a>.
+-->
+ <a href="http://forrest.apache.org/docs/dtd-docs.html">DTD reference documentation</a>.
+ </p>
+ <section id="changes-20">
+ <title>Changes between document-v13 and document-v20</title>
+ <ul>
+ <li>Renamed <strong><link></strong>
+ to <strong><a></strong>
+ </li>
+ <li>Removed <strong><fork></strong>
+ and <strong><jump></strong> in favour of the
+ <strong><a></strong> element. See demonstration
+ <a href="#link-class">using class attribute on links</a>.
+ </li>
+ </ul>
+ </section>
+ <section id="changes-13">
+ <title>Changes between document-v12 and document-v13</title>
+ <p>
+ All v1.2 docs will work fine as v1.3 DTD. The main change is the
+ addition of a @class attribute to every element, which enables the
+ "extra-css" section in the skinconf to be put to good use.
+ </p>
+ </section>
+ <section id="changes-12">
+ <title>Changes between document-v11 and document-v12</title>
+ <p>
+ doc-v12 enhances doc-v11 by relaxing various restrictions that were
+ found to be unnecessary.
+ </p>
+ <ul>
+ <li>
+ Links ((link|jump|fork) and inline elements (br|img|icon|acronym) are
+ allowed inside title.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), table and figure|anchor are
+ allowed inside li.
+ </li>
+ <li>
+ Paragraphs (p|source|note|warning|fixme), lists (ol|ul|dl), table,
+ figure|anchor are allowed inside definition lists (dd) and tables (td
+ and dh).
+ </li>
+ <li>
+ Inline content
+ (strong|em|code|sub|sup|br|img|icon|acronym|link|jump|fork) is
+ allowed in strong and em.
+ </li>
+ </ul>
+ </section>
+ </section>
+ </body>
+ <footer>
+ <legal>This is a legal notice, so it is <strong>important</strong>.</legal>
+ </footer>
+</document>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Static content - including raw un-processed files and documents</title>
+ </header>
+ <body>
+ <section>
+ <title>Linking to static content</title>
+ <p>
+ You can place some types of raw content into the xdocs directory. For example,
+ you can place a PDF file in <code>src/documentation/content/xdocs</code> and link
+ to it normally,
+ <strong><a href="../hello.pdf"></strong><a href="../hello.pdf">hello.pdf</a><strong></a></strong>
+ However, note that if the file is one that Forrest is able to process, for example
+ an HTML file, these files will be processed accordingly.</p>
+
+ <p>
+ It is also worth noting that files in the xdocs directory will only be copied
+ into your final site if there is a link to them somewhere in the site. See the next
+ section for details of how to include content that is not linked.</p>
+
+ <p>
+ For more information see the
+ <a href="site:linking">Linking demonstration</a>.</p>
+ </section>
+
+ <section>
+ <title>Including Static Content that is Not Linked</title>
+
+ <p>
+ You can include raw HTML, PDFs, plain-text, and other files. In your final site by
+ placing them in the <code>src/documentation/content</code> directory. Files in this
+ directory will be copied over automatically but will not be processed in any way by
+ Forrest, that is they will be linked to as raw files.</p>
+
+ <p>
+ You can also have sub-directories such as
+ <code>src/documentation/content/samples/subdir/</code> which
+ reflects your main
+ <code>xdocs/</code> tree. The raw files will then end up
+ beside your documents.
+ </p>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE book PUBLIC "-//APACHE//DTD Cocoon Documentation Book V1.0//EN" "http://forrest.apache.org/dtd/book-cocoon-v10.dtd">
+
+<!-- Sample book.xml file. If this file is renamed to 'book.xml', it will be
+used to define the menu in this subdirectory, instead of that generated from
+the usual site.xml mechanism. The normal relative and absolute hrefs are also
+available. -->
+
+<book software="MyProj"
+ title="MyProj"
+ copyright="@year@ The Apache Software Foundation"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <menu label="About">
+ <!-- using the normal site.xml linking mechanism -->
+ <menu-item label="Index" href="site:index"/>
+ <!-- using relative URIs with relative path -->
+ <menu-item label="Sample page" href="../sample.html"/>
+ <!-- using relative URIs with absolute path -->
+ <menu-item label="Sample ihtml page" href="/samples/ihtml-sample.html"/>
+ <!-- using the normal site.xml linking mechanism -->
+ <menu-item label="FAQ" href="site:faq"/>
+ <menu-item label="Changes" href="site:changes"/>
+ <menu-item label="Todo" href="site:todo"/>
+ </menu>
+
+ <menu label="Subdir">
+ <menu-item label="index" href="site:subdir/index"/>
+ </menu>
+
+</book>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Page generated from a sub-directory</title>
+
+ <authors>
+ <person name="Joe Bloggs" email="joe@joescompany.org" />
+ </authors>
+ </header>
+
+ <body>
+ <section>
+ <title>A sub-directory</title>
+ <p>This was generated from a sub-directory.</p>
+ <p>When creating new subdirectories, remember that these <em>must</em>
+ be declared in site.xml or each directory must have a book.xml file.
+ </p>
+ </section>
+ </body>
+</document>
+
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd">
+<document>
+ <header>
+ <title>Interactive client-side imagemaps - the usemap attribute</title>
+ </header>
+ <body>
+ <section id="demo">
+ <title>Imagemap demo</title>
+ <p>
+ <img src="/images/usemap.gif" usemap="#my-map"
+ alt="usemap demo" width="256" height="256"/>
+ </p>
+ <p>
+ <map name="my-map">
+ <area shape="rect" coords="173,14,240,71"
+ alt="Rectangle" href="ext:forrest"/>
+ <area shape="circle" coords="53,172,28"
+ alt="Circle" href="../index.html"/>
+ <area shape="default" coords="0,0.256,256"
+ alt="Default" href="http://www.apache.org"/>
+ </map>
+ </p>
+ </section>
+ <section id="source">
+ <title>Source code</title>
+ <source><![CDATA[
+ <p>
+ <img src="/images/usemap.gif" usemap="#my-map"
+ alt="usemap demo" width="256" height="256"/>
+ </p>
+ <p>
+ <map name="my-map">
+ <area shape="rect" coords="173,14,240,71"
+ alt="Rectangle" href="ext:forrest"/>
+ <area shape="circle" coords="53,172,28"
+ alt="Circle" href="../index.html"/>
+ <area shape="default" coords="0,0.256,256"
+ alt="Default" href="http://www.apache.org"/>
+ </map>
+ </p>
+]]></source>
+ </section>
+ </body>
+</document>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+Forrest site.xml
+
+This file contains an outline of the site's information content. It is used to:
+- Generate the website menus (though these can be overridden - see docs)
+- Provide semantic, location-independent aliases for internal 'site:' URIs, eg
+<link href="site:changes"> links to changes.html (or ../changes.html if in
+ subdir).
+- Provide aliases for external URLs in the external-refs section. Eg, <link
+ href="ext:cocoon"> links to http://cocoon.apache.org/
+
+See http://forrest.apache.org/docs/linking.html for more info
+-->
+
+<site label="MyProj" href="" xmlns="http://apache.org/forrest/linkmap/1.0" tab="">
+ <!-- Note: No matter what you configure here, Forrest will always try to load
+ index.html when you request http://yourHost/.
+ 'How can I use a start-up-page other than index.html?' in the FAQs has more
+ information tells you how to change that.
+ -->
+ <about label="About">
+ <index label="Index" href="index.html" description="Welcome to the KiSS crawler"/>
+ </about>
+
+ <!-- samples label="Samples" href="samples/" tab="samples">
+ <index href="index.html"/>
+ <sample label="Apache doc v2.0" href="sample.html"
+ description="A nonsense document using all possible elements in the current document v2.0">
+ <top href="#top"/>
+ <section href="#section"/>
+ </sample>
+ <document-v13 label="Apache doc v1.3" href="document-v13.html"
+ description="A nonsense document using all possible elements in the document v1.3">
+ <top href="#top"/>
+ <section href="#section"/>
+ </document-v13>
+ <static label="Static content" href="static.html"
+ description="Static raw un-processed content" />
+ <linking label="Linking" href="linking.html"
+ description="Linking explained and demonstrated" />
+ <sample-html label="Embedded HTML" href="embedded_html.html"
+ description="Test of Embedded HTML" />
+ <sample-ascii-art label="ascii-art page" href="ascii-art.html"
+ description="Sample Ascii Art page" />
+ <sample-usemap label="usemap" href="usemap.html"
+ description="Client-side imagemap" />
+ <sample-custom label="User Schemas" href="customSchema.html"
+ description="Custom XML Schemas"/>
+ <custom label="Custom File" href="custom.html" description="A custom XML file"/>
+ <faq label="FAQ" href="faq.html" description="Frequently Asked Questions" />
+ <subdir label="Subdir" href="subdir/">
+ <index label="Index" href="index.html"
+ description="Page generated from a sub-directory"/>
+ </subdir>
+ </samples -->
+
+ <!-- plugins label="Plugins" href="pluginDocs/plugins_0_70/" tab="plugins">
+ <index label="Index" href="index.html" description="List of plugins available for Forrest"/>
+ </plugins -->
+
+ <files>
+ <hello_print href="hello.pdf" />
+ <test1 href="test1.html" />
+ </files>
+
+
+
+ <!--
+ The href must be wholesite.html/pdf You can change the labels and node names
+ <all label="All">
+ <whole_site_html label="Whole Site HTML" href="wholesite.html"/>
+ <whole_site_pdf label="Whole Site PDF" href="wholesite.pdf"/>
+ </all>
+ -->
+
+ <links>
+ <kiss href="http://www.kiss-technology.com"/>
+ </links>
+
+ <external-refs>
+ <forrest href="http://forrest.apache.org/">
+ <linking href="docs/linking.html"/>
+ <validation href="docs/validation.html"/>
+ <webapp href="docs/your-project.html#webapp"/>
+ <dtd-docs href="docs/dtd-docs.html"/>
+ </forrest>
+ <cocoon href="http://cocoon.apache.org/"/>
+ <xml.apache.org href="http://xml.apache.org/"/>
+ </external-refs>
+
+</site>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE tabs PUBLIC "-//APACHE//DTD Cocoon Documentation Tab V1.1//EN" "http://forrest.apache.org/dtd/tab-cocoon-v11.dtd">
+
+<tabs software="MyProj"
+ title="MyProj"
+ copyright="Foo"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <!-- The rules for tabs are:
+ @dir will always have '/@indexfile' added.
+ @indexfile gets appended to @dir if the tab is selected. Defaults to 'index.html'
+ @href is not modified unless it is root-relative and obviously specifies a
+ directory (ends in '/'), in which case /index.html will be added
+ If @id's are present, site.xml entries with a matching @tab will be in that tab.
+
+ Tabs can be embedded to a depth of two. The second level of tabs will only
+ be displayed when their parent tab is selected.
+ -->
+
+ <tab id="" label="Home" dir="" indexfile="index.html"/>
+
+
+ <!-- tab id="samples" label="Samples" dir="samples" indexfile="sample.html">
+ <tab id="samples-index" label="Index" dir="samples" indexfile="index.html"/>
+ <tab id="samples-sample2" label="Sample2" dir="samples" indexfile="static.html"/>
+ </tab -->
+ <!-- tab label="Apache XML Projects" href="http://xml.apache.org">
+ <tab label="Forrest" href="http://forrest.apache.org"/>
+ <tab label="Xerces" href="http://xml.apache.org/xerces"/>
+ </tab -->
+ <!-- tab id="plugins" label="Plugins" dir="pluginDocs/plugins_0_70" indexfile="index.html"/ -->
+ <!-- Add new tabs here, eg:
+ <tab label="How-Tos" dir="community/howto/"/>
+ <tab label="XML Site" dir="xml-site/"/>
+ -->
+
+</tabs>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"
+"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
+
+<!-- OASIS XML Catalog for Forrest Documents -->
+<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+ prefer="public">
+
+<!-- Download -->
+<public publicId="-//Acme//DTD Hello Document V1.0//EN"
+ uri="hello-v10.dtd"/>
+
+</catalog>
--- /dev/null
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!-- ===================================================================
+
+ Apache Hello DTD (Version 1.0)
+
+PURPOSE:
+
+
+TYPICAL INVOCATION:
+
+ <!DOCTYPE greeting PUBLIC
+ "-//Acme//DTD Hello Document Vx.y//EN"
+ "hello.dtd">
+
+ where
+
+ x := major version
+ y := minor version
+
+FIXME:
+
+CHANGE HISTORY:
+[Version 1.0]
+ 20041009 Initial version. (JJP)
+
+==================================================================== -->
+
+<!-- =============================================================== -->
+<!-- Greeting type element -->
+<!-- =============================================================== -->
+
+<!ELEMENT greeting (#PCDATA)>
+
+<!-- =============================================================== -->
+<!-- End of DTD -->
+<!-- =============================================================== -->
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--+
+ | Transforms Hello document format.
+ +-->
+
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:template match="/">
+ <document>
+ <header>
+ <title>
+ <xsl:value-of select="greeting"/>
+ </title>
+ </header>
+ <body>
+ <xsl:value-of select="greeting"/>
+ </body>
+ </document>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2005 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+
+ <map:components>
+ <map:actions>
+ <map:action logger="sitemap.action.sourcetype" name="sourcetype" src="org.apache.forrest.sourcetype.SourceTypeAction">
+ <sourcetype name="hello-v1.0">
+ <document-declaration public-id="-//Acme//DTD Hello Document V1.0//EN" />
+ </sourcetype>
+ </map:action>
+ </map:actions>
+
+ <map:selectors default="parameter">
+ <map:selector logger="sitemap.selector.parameter" name="parameter" src="org.apache.cocoon.selection.ParameterSelector" />
+ </map:selectors>
+ </map:components>
+
+ <map:resources>
+ <map:resource name="transform-to-document">
+ <map:act type="sourcetype" src="{src}">
+ <map:select type="parameter">
+ <map:parameter name="parameter-selector-test" value="{sourcetype}" />
+
+ <map:when test="hello-v1.0">
+ <map:generate src="{project:content.xdocs}{../../1}.xml" />
+ <map:transform src="{project:resources.stylesheets}/hello2document.xsl" />
+ <map:serialize type="xml-document"/>
+ </map:when>
+ </map:select>
+ </map:act>
+ </map:resource>
+ </map:resources>
+
+ <map:pipelines>
+ <map:pipeline>
+ <map:match pattern="old_site/*.html">
+ <map:select type="exists">
+ <map:when test="{project:content}{1}.html">
+ <map:read src="{project:content}{1}.html" mime-type="text/html"/>
+ <!--
+ Use this instead if you want JTidy to clean up your HTML
+ <map:generate type="html" src="{project:content}/{0}" />
+ <map:serialize type="html"/>
+ -->
+ </map:when>
+ </map:select>
+ </map:match>
+
+
+ <map:match pattern="installs/**">
+ <map:read src="../../../{1}"/>
+ </map:match>
+
+ <map:match pattern="**.xml">
+ <map:call resource="transform-to-document">
+ <map:parameter name="src" value="{project:content.xdocs}{1}.xml" />
+ </map:call>
+ </map:match>
+ </map:pipeline>
+ </map:pipelines>
+</map:sitemap>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2005 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+
+<!--
+Skin configuration file. This file contains details of your project,
+which will be used to configure the chosen Forrest skin.
+-->
+
+<!DOCTYPE skinconfig PUBLIC "-//APACHE//DTD Skin Configuration V0.7-1//EN" "http://forrest.apache.org/dtd/skinconfig-v07-1.dtd">
+<skinconfig>
+ <!-- To enable lucene search add provider="lucene" (default is google).
+ Add box-location="alt" to move the search box to an alternate location
+ (if the skin supports it) and box-location="all" to show it in all
+ available locations on the page. Remove the <search> element to show
+ no search box. @domain will enable sitesearch for the specific domain with google.
+ In other words google will search the @domain for the query string.
+ -->
+ <search name="MyProject" domain="mydomain" provider="google"/>
+
+ <!-- Disable the print link? If enabled, invalid HTML 4.0.1 -->
+ <disable-print-link>true</disable-print-link>
+ <!-- Disable the PDF link? -->
+ <disable-pdf-link>false</disable-pdf-link>
+ <!-- Disable the POD link? -->
+ <disable-pod-link>true</disable-pod-link>
+ <!-- Disable the Text link? FIXME: NOT YET IMPLEMENETED. -->
+ <disable-txt-link>true</disable-txt-link>
+ <!-- Disable the xml source link? -->
+ <!-- The xml source link makes it possible to access the xml rendition
+ of the source frim the html page, and to have it generated statically.
+ This can be used to enable other sites and services to reuse the
+ xml format for their uses. Keep this disabled if you don't want other
+ sites to easily reuse your pages.-->
+ <disable-xml-link>true</disable-xml-link>
+
+ <!-- Disable navigation icons on all external links? -->
+ <disable-external-link-image>true</disable-external-link-image>
+
+ <!-- Disable w3c compliance links?
+ Use e.g. align="center" to move the compliance links logos to
+ an alternate location default is left.
+ (if the skin supports it) -->
+ <disable-compliance-links>false</disable-compliance-links>
+
+ <!-- Render mailto: links unrecognisable by spam harvesters? -->
+ <obfuscate-mail-links>true</obfuscate-mail-links>
+ <obfuscate-mail-value>.at.</obfuscate-mail-value>
+
+ <!-- Disable the javascript facility to change the font size -->
+ <disable-font-script>true</disable-font-script>
+
+ <!-- mandatory project logo
+ default skin: renders it at the top -->
+ <project-name>KiSS Crawler</project-name>
+ <project-description>Automatic recording for KiSS harddisk recorders</project-description>
+ <project-url>http://kiss.wamblee.org</project-url>
+ <project-logo>images/project.png</project-logo>
+ <!-- Alternative static image:
+ <project-logo>images/project-logo.gif</project-logo> -->
+
+ <!-- optional group logo
+ default skin: renders it at the top-left corner -->
+ <group-name>wamblee.org</group-name>
+ <group-description></group-description>
+ <group-url>http://wamblee.org</group-url>
+ <group-logo>images/group.png</group-logo>
+ <!-- Alternative static image:
+ <group-logo>images/group-logo.gif</group-logo> -->
+
+ <!-- optional host logo (e.g. sourceforge logo)
+ default skin: renders it at the bottom-left corner -->
+ <host-url></host-url>
+ <host-logo></host-logo>
+
+ <!-- relative url of a favicon file, normally favicon.ico -->
+ <favicon-url></favicon-url>
+
+ <!-- The following are used to construct a copyright statement -->
+ <year>2006</year>
+ <vendor>wamblee.org</vendor>
+ <!-- The optional copyright-link URL will be used as a link in the
+ copyright statement
+ <copyright-link>http://www.apache.org/licenses/</copyright-link>
+ -->
+
+ <!-- Some skins use this to form a 'breadcrumb trail' of links.
+ Use location="alt" to move the trail to an alternate location
+ (if the skin supports it).
+ Omit the location attribute to display the trail in the default location.
+ Use location="none" to not display the trail (if the skin supports it).
+ For some skins just set the attributes to blank.
+
+ NOTE: If a breadcrumb entry points at a local file the href must
+ be complete, that is it must point to the file itself, not to a
+ directory.
+ -->
+ <trail>
+ <link1 name="wamblee.org" href="http://wamblee.org/"/>
+ <link2 name="utils" href="http://wamblee.org/utils"/>
+ <link3 name="KiSS crawler" href="http://kiss.wamblee.org"/>
+ </trail>
+
+ <!-- Configure the TOC, i.e. the Table of Contents.
+ @max-depth
+ how many "section" levels need to be included in the
+ generated Table of Contents (TOC).
+ @min-sections
+ Minimum required to create a TOC.
+ @location ("page","menu","page,menu", "none")
+ Where to show the TOC.
+ -->
+ <toc max-depth="2" min-sections="1" location="page"/>
+
+ <!-- Heading types can be clean|underlined|boxed -->
+ <headings type="boxed"/>
+
+ <!-- The optional feedback element will be used to construct a
+ feedback link in the footer with the page pathname appended:
+ <a href="@href">{@to}</a>
+ -->
+ <feedback to="erik@wamblee.org"
+ href="mailto:erik@wamblee.org?subject=Feedback " >
+ Send feedback about the website to:
+ </feedback>
+
+ <!-- Optional message of the day (MOTD).
+ Note: This is only implemented in the pelt skin.
+ If the optional <motd> element is used, then messages will be appended
+ depending on the URI string pattern.
+ motd-option : Specifies a pattern to match and provides small text content.
+ motd-title : This text will be added in brackets after the <html><title>
+ motd-page : This text will be added in a panel on the face of the page,
+ with the "motd-page-url" being the hyperlink "More".
+ Values for the "location" attribute are:
+ page : on the face of the page, e.g. in the spare space of the toc
+ alt : at the bottom of the left-hand navigation panel
+ both : both
+ -->
+<!--
+ <motd>
+ <motd-option pattern="docs_0_80">
+ <motd-title>v0.8-dev</motd-title>
+ <motd-page location="both">
+ This is documentation for development version v0.8
+ </motd-page>
+ <motd-page-url>/versions/index.html</motd-page-url>
+ </motd-option>
+ <motd-option pattern="docs_0_70">
+ <motd-title>v0.7</motd-title>
+ <motd-page location="both">
+ This is documentation for current version v0.7
+ </motd-page>
+ <motd-page-url>/versions/index.html</motd-page-url>
+ </motd-option>
+ </motd>
+-->
+
+ <!--
+ extra-css - here you can define custom css-elements that are
+ A) overriding the fallback elements or
+ B) adding the css definition from new elements that you may have
+ used in your documentation.
+ -->
+ <extra-css>
+ <!--Example of reason B:
+ To define the css definition of a new element that you may have used
+ in the class attribute of a <p> node.
+ e.g. <p class="quote"/>
+ -->
+ p.quote {
+ margin-left: 2em;
+ padding: .5em;
+ background-color: #f0f0f0;
+ font-family: monospace;
+ }
+ <!--Example:
+ To override the colours of links only in the footer.
+ -->
+ #footer a { color: #0F3660; }
+ #footer a:visited { color: #009999; }
+ </extra-css>
+
+ <colors>
+ <!-- These values are used for the generated CSS files.
+ They essentially "override" the default colors defined in the chosen skin.
+ There are four duplicate "groups" of colors below, denoted by comments:
+ Color group: Forrest, Krysalis, Collabnet, and Lenya using Pelt.
+ They are provided for example only. To customize the colors of any skin,
+ uncomment one of these groups of color elements and change the values
+ of the particular color elements that you wish to change.
+ Note that by default, all color groups are commented-out which means that
+ the default colors provided by the skin are being used.
+ -->
+
+ <!-- Color group: Forrest: example colors similar to forrest.apache.org
+ Some of the element names are obscure, so comments are added to show how
+ the "pelt" skin uses them, other skins might use these elements in a different way.
+ Tip: temporarily change the value of an element to red (#ff0000) and see the effect.
+ pelt: breadtrail: the strip at the top of the page and the second strip under the tabs
+ pelt: header: top strip containing project and group logos
+ pelt: heading|subheading: section headings within the content
+ pelt: navstrip: the strip under the tabs which contains the published date
+ pelt: menu: the left-hand navigation panel
+ pelt: toolbox: the selected menu item
+ pelt: searchbox: the background of the searchbox
+ pelt: border: line border around selected menu item
+ pelt: body: any remaining parts, e.g. the bottom of the page
+ pelt: footer: the second from bottom strip containing credit logos and published date
+ pelt: feedback: the optional bottom strip containing feedback link
+ -->
+<!--
+ <color name="breadtrail" value="#cedfef" font="#0F3660" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="header" value="#294563"/>
+ <color name="tab-selected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="tab-unselected" value="#b5c7e7" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="subtab-selected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="subtab-unselected" value="#4a6d8c" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="heading" value="#294563"/>
+ <color name="subheading" value="#4a6d8c"/>
+ <color name="published" value="#4C6C8F" font="#FFFFFF"/>
+ <color name="feedback" value="#4C6C8F" font="#FFFFFF" align="center"/>
+ <color name="navstrip" value="#4a6d8c" font="#ffffff" link="#0F3660" vlink="#0F3660" hlink="#000066"/>
+ <color name="menu" value="#4a6d8c" font="#cedfef" link="#ffffff" vlink="#ffffff" hlink="#ffcf00"/>
+ <color name="toolbox" value="#4a6d8c"/>
+ <color name="border" value="#294563"/>
+ <color name="dialog" value="#4a6d8c"/>
+ <color name="searchbox" value="#4a6d8c" font="#000000"/>
+ <color name="body" value="#ffffff" link="#0F3660" vlink="#009999" hlink="#000066"/>
+ <color name="table" value="#7099C5"/>
+ <color name="table-cell" value="#f0f0ff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#CFDCED"/>
+ <color name="footer" value="#cedfef"/>
+-->
+
+ <!-- Color group: Krysalis -->
+<!--
+ <color name="header" value="#FFFFFF"/>
+
+ <color name="tab-selected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="tab-unselected" value="#F7F7F7" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-selected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#a5b6c6" link="#000000" vlink="#000000" hlink="#000000"/>
+
+ <color name="heading" value="#a5b6c6"/>
+ <color name="subheading" value="#CFDCED"/>
+
+ <color name="navstrip" value="#CFDCED" font="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="toolbox" value="#a5b6c6"/>
+ <color name="border" value="#a5b6c6"/>
+
+ <color name="menu" value="#F7F7F7" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="dialog" value="#F7F7F7"/>
+
+ <color name="body" value="#ffffff" link="#0F3660" vlink="#009999" hlink="#000066"/>
+
+ <color name="table" value="#a5b6c6"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#a5b6c6"/>
+
+ <color name="footer" value="#a5b6c6"/>
+-->
+
+ <!-- Color group: Collabnet -->
+<!--
+ <color name="header" value="#003366"/>
+
+ <color name="tab-selected" value="#dddddd" link="#555555" vlink="#555555" hlink="#555555"/>
+ <color name="tab-unselected" value="#999999" link="#ffffff" vlink="#ffffff" hlink="#ffffff"/>
+ <color name="subtab-selected" value="#cccccc" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#cccccc" link="#555555" vlink="#555555" hlink="#555555"/>
+
+ <color name="heading" value="#003366"/>
+ <color name="subheading" value="#888888"/>
+
+ <color name="navstrip" value="#dddddd" font="#555555"/>
+ <color name="toolbox" value="#dddddd" font="#555555"/>
+ <color name="border" value="#999999"/>
+
+ <color name="menu" value="#ffffff"/>
+ <color name="dialog" value="#eeeeee"/>
+
+ <color name="body" value="#ffffff"/>
+
+ <color name="table" value="#ccc"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#003366"/>
+
+ <color name="footer" value="#ffffff"/>
+-->
+ <!-- Color group: Lenya using pelt-->
+<!--
+
+ <color name="header" value="#ffffff"/>
+
+ <color name="tab-selected" value="#E5E4D9" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="tab-unselected" value="#F5F4E9" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-selected" value="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="subtab-unselected" value="#E5E4D9" link="#000000" vlink="#000000" hlink="#000000"/>
+
+ <color name="heading" value="#E5E4D9"/>
+ <color name="subheading" value="#000000"/>
+ <color name="published" value="#000000"/>
+ <color name="navstrip" value="#E5E4D9" font="#000000"/>
+ <color name="toolbox" value="#CFDCED" font="#000000"/>
+ <color name="border" value="#999999"/>
+
+ <color name="menu" value="#E5E4D9" font="#000000" link="#000000" vlink="#000000" hlink="#000000"/>
+ <color name="dialog" value="#CFDCED"/>
+ <color name="body" value="#ffffff" />
+
+ <color name="table" value="#ccc"/>
+ <color name="table-cell" value="#ffffff"/>
+ <color name="highlight" value="#ffff00"/>
+ <color name="fixme" value="#cc6600"/>
+ <color name="note" value="#006699"/>
+ <color name="warning" value="#990000"/>
+ <color name="code" value="#003366"/>
+
+ <color name="footer" value="#E5E4D9"/>
+-->
+ </colors>
+
+ <!-- Settings specific to PDF output. -->
+ <pdf>
+ <!--
+ Supported page sizes are a0, a1, a2, a3, a4, a5, executive,
+ folio, legal, ledger, letter, quarto, tabloid (default letter).
+ Supported page orientations are portrait, landscape (default
+ portrait).
+ Supported text alignments are left, right, justify (default left).
+ -->
+ <page size="letter" orientation="portrait" text-align="left"/>
+
+ <!--
+ Pattern of the page numbering in the footer - Default is "Page x".
+ first occurrence of '1' digit represents the current page number,
+ second occurrence of '1' digit represents the total page number,
+ anything else is considered as the static part of the numbering pattern.
+ Examples : x is the current page number, y the total page number.
+ <page-numbering-format>none</page-numbering-format> Do not displays the page numbering
+ <page-numbering-format>1</page-numbering-format> Displays "x"
+ <page-numbering-format>p1.</page-numbering-format> Displays "px."
+ <page-numbering-format>Page 1/1</page-numbering-format> Displays "Page x/y"
+ <page-numbering-format>(1-1)</page-numbering-format> Displays "(x-y)"
+ -->
+ <page-numbering-format>Page 1</page-numbering-format>
+
+ <!--
+ Margins can be specified for top, bottom, inner, and outer
+ edges. If double-sided="false", the inner edge is always left
+ and the outer is always right. If double-sided="true", the
+ inner edge will be left on odd pages, right on even pages,
+ the outer edge vice versa.
+ Specified below are the default settings.
+ -->
+ <margins double-sided="false">
+ <top>1in</top>
+ <bottom>1in</bottom>
+ <inner>1.25in</inner>
+ <outer>1in</outer>
+ </margins>
+
+ <!--
+ Print the URL text next to all links going outside the file
+ -->
+ <show-external-urls>false</show-external-urls>
+
+ <!--
+ Disable the copyright footer on each page of the PDF.
+ A footer is composed for each page. By default, a "credit" with role=pdf
+ will be used, as explained below. Otherwise a copyright statement
+ will be generated. This latter can be disabled.
+ -->
+ <disable-copyright-footer>false</disable-copyright-footer>
+ </pdf>
+
+ <!--
+ Credits are typically rendered as a set of small clickable
+ images in the page footer.
+
+ Use box-location="alt" to move the credits to an alternate location
+ (if the skin supports it).
+
+ For example, pelt skin:
+ - box-location="alt" will place the logo at the end of the
+ left-hand coloured menu panel.
+ - box-location="alt2" will place them underneath that panel
+ in the left-hand whitespace.
+ - Otherwise they are placed next to the compatibility icons
+ at the bottom of the screen.
+
+ Comment out the whole <credit>-element if you want no credits in the
+ web pages
+ -->
+ <credits>
+ <credit box-location="alt">
+ <name>Built with Apache Forrest</name>
+ <url>http://forrest.apache.org/</url>
+ <image>images/built-with-forrest-button.png</image>
+ <width>88</width>
+ <height>31</height>
+ </credit>
+ <!-- A credit with @role="pdf" will be used to compose a footer
+ for each page in the PDF, using either "name" or "url" or both.
+ -->
+ <!--
+ <credit role="pdf">
+ <name>Built with Apache Forrest</name>
+ <url>http://forrest.apache.org/</url>
+ </credit>
+ -->
+ </credits>
+
+</skinconfig>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<!--
+ This catalog is used so displays how the language name
+ is named by their speakers.
+-->
+<catalogue >
+ <message key="en">English</message>
+ <message key="es">Espanol</message>
+ <message key="it">Italiano</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="en">English</message>
+ <message key="es">Spanish</message>
+ <message key="nl">Dutch</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="en">Inglés</message>
+ <message key="es">Español</message>
+ <message key="nl">Holandés</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="About">About</message>
+ <message key="Index">Index</message>
+ <message key="Changes">Changes</message>
+ <message key="Todo">Todo</message>
+ <message key="Samples">Samples</message>
+ <message key="Apache document">Apache document</message>
+ <message key="Static content">Static content</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki page</message>
+ <message key="ihtml page">Ihtml page</message>
+ <message key="ehtml page">Ehtml page</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">XSP page</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="af">
+ <message key="About">Aangaande</message>
+ <message key="Index">Inhoud</message>
+ <message key="Changes">Veranderinge</message>
+ <message key="Todo">Om te doen</message>
+ <message key="Samples">Voorbeelde</message>
+ <message key="Apache document">Apache dokument</message>
+ <message key="Static content">Statise Inhoud</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki bladsy</message>
+ <message key="ihtml page">Ihtml bladsy</message>
+ <message key="ehtml page">Ehtml bladsy</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Vereenvoudigde Docbook</message>
+ <message key="XSP page">XSP bladsy</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="de">
+ <message key="About">Über</message>
+ <message key="Index">Index</message>
+ <message key="Changes">Änderungen </message>
+ <message key="Todo">Todo</message>
+ <message key="Samples">Beispiele</message>
+ <message key="Apache document">Apache Dokumentationsseite</message>
+ <message key="Static content">Statischer Inhalt</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki Seite</message>
+ <message key="ihtml page">ihtml Seite</message>
+ <message key="ehtml page">ehtml Seite</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Vereinfachte Docbook</message>
+ <message key="XSP page">XSP Seite</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="About">Acerca de</message>
+ <message key="Index">Indice</message>
+ <message key="Changes">Cambios</message>
+ <message key="Todo">Tareas pendientes</message>
+ <message key="Samples">Ejemplos</message>
+ <message key="Apache document">Documento Apache</message>
+ <message key="Static content">Contenido Estático</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Página Wiki</message>
+ <message key="ihtml page">Página ihtml</message>
+ <message key="ehtml page">Página ehtml</message>
+ <message key="FAQ">Preguntas Frecuentes</message>
+ <message key="Simplifed Docbook">Página Simplifed Docbook</message>
+ <message key="XSP page">Página XSP</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="it">
+ <message key="About">Riguardo a</message>
+ <message key="Index">Indice</message>
+ <message key="Changes">Cambiamenti</message>
+ <message key="Todo">Cose da fare</message>
+ <message key="Samples">Esempi</message>
+ <message key="Apache document">Apache document</message>
+ <message key="Static content">Contenuto Statico</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Pagina Wiki</message>
+ <message key="ihtml page">Pagina ihtml</message>
+ <message key="ehtml page">Pagina ehtml</message>
+ <message key="FAQ">Domande frequenti</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">Pagina XSP</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="no">
+ <message key="About">Om</message>
+ <message key="Index">Indeks</message>
+ <message key="Changes">Endringer</message>
+ <message key="Todo">Oppgave liste</message>
+ <message key="Samples">Eksempler</message>
+ <message key="Apache document">Apache Dokument</message>
+ <message key="Static content">Statisk innhold</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki side</message>
+ <message key="ihtml page">ihtml side</message>
+ <message key="ehtml page">ehtml side</message>
+ <message key="FAQ">FAQ</message>
+ <message key="Simplifed Docbook">Simplifed Docbook</message>
+ <message key="XSP page">XSP side</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="ru">
+ <message key="About">О проекте</message>
+ <message key="Index">Содержание</message>
+ <message key="Changes">Изменения</message>
+ <message key="Todo">План</message>
+ <message key="Samples">Примеры</message>
+ <message key="Apache document">Страница документа Apache</message>
+ <message key="Static content">Статическое содержание</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Страница Wiki</message>
+ <message key="ihtml page">Страница ihtml</message>
+ <message key="ehtml page">Страница ehtml</message>
+ <message key="FAQ">Вопросы/Ответы</message>
+ <message key="Simplifed Docbook">Docbook страница</message>
+ <message key="XSP page">XSP страница</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="sk">
+ <message key="About">O programe</message>
+ <message key="Index">Zoznám</message>
+ <message key="Changes">Zmeny</message>
+ <message key="Todo">Úlohy</message>
+ <message key="Samples">Príklady</message>
+ <message key="Apache document">Apache Document</message>
+ <message key="Static content">Statický Obsah</message>
+ <message key="Linking">Linking</message>
+ <message key="Wiki page">Wiki stránka</message>
+ <message key="ihtml page">ihtml stránka</message>
+ <message key="ehtml page">ehtml stránka</message>
+ <message key="FAQ">Casté Otázky</message>
+ <message key="Simplifed Docbook">Simplifed Docbook stránka</message>
+ <message key="XSP page">XSP stránka</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="en">
+ <message key="Home">Home</message>
+ <message key="Samples">Samples</message>
+ <message key="Apache XML Projects">Apache XML Projects</message>
+</catalogue>
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ Copyright 2002-2004 The Apache Software Foundation or its licensors,
+ as applicable.
+
+ 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.
+-->
+<catalogue xml:lang="es">
+ <message key="Home">Inicio</message>
+ <message key="Samples">Ejemplos</message>
+ <message key="Apache XML Projects">Projectos XML Apache</message>
+</catalogue>
--- /dev/null
+# Copyright 2002-2005 The Apache Software Foundation or its licensors,
+# as applicable.
+#
+# 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.
+
+##############
+# Properties used by forrest.build.xml for building the website
+# These are the defaults, un-comment them only if you need to change them.
+##############
+
+# Prints out a summary of Forrest settings for this project
+#forrest.echo=true
+
+# Project name (used to name .war file)
+#project.name=my-project
+
+# Specifies name of Forrest skin to use
+# See list at http://forrest.apache.org/docs/skins.html
+#project.skin=pelt
+
+# Descriptors for plugins and skins
+# comma separated list, file:// is supported
+#forrest.skins.descriptors=http://forrest.apache.org/skins/skins.xml,file:///c:/myskins/skins.xml
+#forrest.plugins.descriptors=http://forrest.apache.org/plugins/plugins.xml,http://forrest.apache.org/plugins/whiteboard-plugins.xml
+
+##############
+# behavioural properties
+#project.menu-scheme=tab_attributes
+#project.menu-scheme=directories
+
+##############
+# layout properties
+
+# Properties that can be set to override the default locations
+#
+# Parent properties must be set. This usually means uncommenting
+# project.content-dir if any other property using it is uncommented
+
+project.build-dir=${project.home}/target/forrest
+
+#project.status=status.xml
+project.content-dir=docs
+#project.raw-content-dir=${project.content-dir}/content
+#project.conf-dir=${project.content-dir}/conf
+#project.sitemap-dir=${project.content-dir}
+#project.xdocs-dir=${project.content-dir}/content/xdocs
+#project.resources-dir=${project.content-dir}/resources
+#project.stylesheets-dir=${project.resources-dir}/stylesheets
+#project.images-dir=${project.resources-dir}/images
+#project.schema-dir=${project.resources-dir}/schema
+#project.skins-dir=${project.content-dir}/skins
+#project.skinconf=${project.content-dir}/skinconf.xml
+#project.lib-dir=${project.content-dir}/lib
+#project.classes-dir=${project.content-dir}/classes
+#project.translations-dir=${project.content-dir}/translations
+
+##############
+# validation properties
+
+# This set of properties determine if validation is performed
+# Values are inherited unless overridden.
+# e.g. if forrest.validate=false then all others are false unless set to true.
+#forrest.validate=true
+#forrest.validate.xdocs=${forrest.validate}
+#forrest.validate.skinconf=${forrest.validate}
+#forrest.validate.sitemap=${forrest.validate}
+#forrest.validate.stylesheets=${forrest.validate}
+#forrest.validate.skins=${forrest.validate}
+#forrest.validate.skins.stylesheets=${forrest.validate.skins}
+
+# *.failonerror=(true|false) - stop when an XML file is invalid
+#forrest.validate.failonerror=true
+
+# *.excludes=(pattern) - comma-separated list of path patterns to not validate
+# e.g.
+#forrest.validate.xdocs.excludes=samples/subdir/**, samples/faq.xml
+#forrest.validate.xdocs.excludes=
+
+
+##############
+# General Forrest properties
+
+# The URL to start crawling from
+#project.start-uri=linkmap.html
+
+# Set logging level for messages printed to the console
+# (DEBUG, INFO, WARN, ERROR, FATAL_ERROR)
+#project.debuglevel=ERROR
+
+# Max memory to allocate to Java
+#forrest.maxmemory=64m
+
+# Any other arguments to pass to the JVM. For example, to run on an X-less
+# server, set to -Djava.awt.headless=true
+#forrest.jvmargs=
+
+# The bugtracking URL - the issue number will be appended
+#project.bugtracking-url=http://issues.apache.org/bugzilla/show_bug.cgi?id=
+#project.bugtracking-url=http://issues.apache.org/jira/browse/
+
+# The issues list as rss
+#project.issues-rss-url=
+
+#I18n Property. Based on the locale request for the browser.
+#If you want to use it for static site then modify the JVM system.language
+# and run once per language
+#project.i18n=true
+
+# The names of plugins that are required to build the project
+# comma separated list (no spaces)
+# You can request a specific version by appending "-VERSION" to the end of
+# the plugin name. If you exclude a version number the latest released version
+# will be used, however, be aware that this may be a development version. In
+# a production environment it is recomended that you specify a known working
+# version.
+# Run "forrest available-plugins" for a list of plug-ins currently available
+project.required.plugins=org.apache.forrest.plugin.output.pdf
+
+# Proxy configuration
+# proxy.host=
+# proxy.port=
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-kiss</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org KiSS crawler</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-basic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-email</groupId>
+ <artifactId>commons-email</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+/**
+ * Abstract visitor of the tv guide with default looping behavior.
+ */
+public abstract class AbstractVisitor implements Visitor {
+
+ /**
+ * Constructs the visitor.
+ *
+ */
+ protected AbstractVisitor() {
+ // Empty
+ }
+
+ /**
+ * Visits the channel by visiting all programs of the channel.
+ *
+ * @param aChannel
+ * Channel to visit.
+ */
+ public void visitChannel(Channel aChannel) {
+ for (Program program : aChannel.getPrograms()) {
+ program.accept(this);
+ }
+ }
+
+ /**
+ * Visits the TV guide by visiting all channels of the guide.
+ *
+ * @param aGuide
+ * TV guide to visit.
+ */
+ public void visitTvGuide(TVGuide aGuide) {
+ for (Channel channel : aGuide.getChannels()) {
+ channel.accept(this);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Represents the programme for a tv channel.
+ */
+public class Channel {
+
+ /**
+ * TV channel name.
+ */
+ private String _name;
+
+ /**
+ * List of programs in chronological order.
+ */
+ private List<Program> _programs;
+
+ /**
+ * Constructs the channel.
+ * @param aName Channel name.
+ * @param aPrograms Programs.
+ */
+ public Channel(String aName, List<Program> aPrograms) {
+ _name = aName;
+ _programs = aPrograms;
+ }
+
+ /**
+ * Gets the channel name.
+ * @return channel name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Gets the list of program.
+ * @return Programs.
+ */
+ public List<Program> getPrograms() {
+ return Collections.unmodifiableList(_programs);
+ }
+
+ /**
+ * Accepts a visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitChannel(this);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+
+/**
+ * Visitor which determines the interesting programs in the TV guide.
+ */
+public class MatchVisitor extends AbstractVisitor {
+
+ /**
+ * Criterion that determines which programs are interesting.
+ */
+ private Condition<Program> _matcher;
+
+ /**
+ * List of interesting programs.
+ */
+ private List<Program> _programs;
+
+ /**
+ * Constructs the visitor.
+ * @param aMatcher Condition describing interesting programs.
+ */
+ public MatchVisitor(Condition<Program> aMatcher) {
+ _matcher = aMatcher;
+ _programs = new ArrayList<Program>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.Visitor#visitProgram(org.wamblee.crawler.kiss.Program)
+ */
+ public void visitProgram(Program aProgram) {
+ if (_matcher.matches(aProgram)) {
+ _programs.add(aProgram);
+ }
+ }
+
+ /**
+ * Gets the list of interesting programs. To be called after applying
+ * the visitor on a tv guide.
+ * @return List of interesting programs.
+ */
+ public List<Program> getMatches() {
+ return _programs;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.io.PrintStream;
+
+
+/**
+ * Print visitor for pretty printing the TV guide.
+ */
+public class PrintVisitor extends AbstractVisitor {
+
+ /**
+ * Stream to print the guide on.
+ */
+ private PrintStream _stream;
+
+ /**
+ * Constructs the print visitor.
+ * @param aStream Stream to print on.
+ */
+ public PrintVisitor(PrintStream aStream) {
+ _stream = aStream;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.Visitor#visitProgram(org.wamblee.crawler.kiss.Program)
+ */
+ public void visitProgram(Program aProgram) {
+ _stream.println(" " + aProgram.toString());
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.AbstractVisitor#visitChannel(org.wamblee.crawler.kiss.Channel)
+ */
+ @Override
+ public void visitChannel(Channel aChannel) {
+ _stream.println(aChannel.getName());
+ super.visitChannel(aChannel);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Comparator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.kiss.main.SystemProperties;
+
+/**
+ * Represents a television program.
+ */
+public class Program {
+
+ private static final String ELEM_PROGRAM = "program";
+
+ private static final String ELEM_NAME = "name";
+
+ private static final String ELEM_KEYWORDS = "keywords";
+
+ private static final String ELEM_DESCRIPTION = "description";
+
+ private static final String ELEM_CHANNEL = "channel";
+
+ private static final String ELEM_INTERVAL = "interval";
+
+ private static final String ELEM_END_TIME = "end";
+
+ private static final String ELEM_BEGIN_TIME = "begin";
+
+ /**
+ * Lexicographical comparison of programs based on (time, title, channel).
+ *
+ */
+ public static class TimeComparator implements Comparator<Program> {
+
+ /**
+ * Lexicographical comparison based on start time, program name, and
+ * channel.
+ *
+ * @param aProgram1
+ * First program.
+ * @param aProgram2
+ * Second program.
+ * @return See {@link Comparator#compare(T, T)}
+ */
+ public int compare(Program aProgram1, Program aProgram2) {
+ int value = aProgram1.getInterval().getBegin().compareTo(
+ aProgram2.getInterval().getBegin());
+ if (value != 0) {
+ return value;
+ }
+ value = aProgram1.getName().compareTo(aProgram2.getName());
+ if (value != 0) {
+ return value;
+ }
+ return aProgram1.getChannel().compareTo(aProgram2.getChannel());
+ }
+ }
+
+ private static final Log LOG = LogFactory.getLog(Program.class);
+
+ /**
+ * Name of the record action on the program details page.
+ */
+ private static final String RECORD_ACTION = "record";
+
+ /**
+ * Result of recording a program.
+ *
+ */
+ public enum RecordingResult {
+ /**
+ * Successfully recorded.
+ */
+ OK,
+
+ /**
+ * Already recorded program.
+ */
+ DUPLICATE,
+
+ /**
+ * Recording conflict with another program.
+ */
+ CONFLICT,
+
+ /**
+ * Program occurred in the past.
+ */
+ OLDSHOW,
+
+ /**
+ * Program could not be recorded for technical reasons.
+ */
+ ERROR;
+ };
+
+ /**
+ * Indent string to use for pretty printing.
+ */
+ private static final String INDENT = " ";
+
+ /**
+ * Channel the program is on.
+ */
+ private String _channel;
+
+ /**
+ * Program name.
+ */
+ private String _name;
+
+ /**
+ * Program description.
+ */
+ private String _description;
+
+ /**
+ * Keywords or classification of the program.
+ */
+ private String _keywords;
+
+ /**
+ * Time interval for the program (from/to).
+ */
+ private TimeInterval _interval;
+
+ /**
+ * Action to execute to obtain program information and/or record the
+ * program.
+ */
+ private Action _programInfo;
+
+ /**
+ * Constructs the program.
+ *
+ * @param aChannel
+ * Channel name.
+ * @param aName
+ * Program name.
+ * @param aDescription
+ * Description.
+ * @param aKeywords
+ * Keywords/classification.
+ * @param aInterval
+ * Time interval.
+ * @param aProgramInfo
+ * Action to execute for detailed program information or for
+ * recording the page.
+ */
+ public Program(String aChannel, String aName, String aDescription,
+ String aKeywords, TimeInterval aInterval, Action aProgramInfo) {
+ _channel = aChannel;
+ _name = aName;
+ _description = aDescription;
+ _keywords = aKeywords;
+ _interval = aInterval;
+ _programInfo = aProgramInfo;
+ }
+
+ /**
+ * Gets the channel.
+ *
+ * @return Channel.
+ */
+ public String getChannel() {
+ return _channel;
+ }
+
+ /**
+ * Gets the program name.
+ *
+ * @return Name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Gets the description.
+ *
+ * @return Description.
+ */
+ public String getDescription() {
+ return _description;
+ }
+
+ /**
+ * Gets the keywords/classification.
+ *
+ * @return Keywords/classification
+ */
+ public String getKeywords() {
+ return _keywords;
+ }
+
+ /**
+ * Gets the time interval.
+ *
+ * @return Time interval.
+ */
+ public TimeInterval getInterval() {
+ return _interval;
+ }
+
+ /**
+ * Checks if recording is possible.
+ *
+ * @return True iff recording is possible.
+ */
+ public boolean isRecordingPossible() {
+ try {
+ return _programInfo.execute().getAction(RECORD_ACTION) != null;
+ } catch (PageException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Records the show.
+ *
+ * @return Status describing the result of recording.
+ */
+ public RecordingResult record() {
+ LOG.info("Recording " + this);
+ if (SystemProperties.isRecordDisabled()) {
+ return RecordingResult.OK;
+ }
+ try {
+ Action record = _programInfo.execute().getAction(RECORD_ACTION);
+ if (record == null) {
+ LOG.info(" result: " + RecordingResult.OLDSHOW);
+ return RecordingResult.OLDSHOW;
+ }
+ Page result = record.execute();
+ RecordingResult recordingResult = RecordingResult.valueOf(result
+ .getContent().getText());
+ LOG.info(" result: " + recordingResult);
+ return recordingResult;
+ } catch (PageException e) {
+ LOG.warn("Technical problem recording program: '" + this + "'", e);
+ LOG.info(" result: " + RecordingResult.ERROR);
+ return RecordingResult.ERROR;
+ }
+ }
+
+ /**
+ * Accepts the visitor.
+ *
+ * @param aVisitor
+ * Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitProgram(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return _interval + " - " + _name + " (" + _channel + "/" + _keywords
+ + ")" + "\n"
+ + (INDENT + _description).replaceAll("\n", "\n" + INDENT);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof Program)) {
+ return false;
+ }
+ Program program = (Program) aObject;
+ return getName().equals(program.getName())
+ && _programInfo.equals(program._programInfo);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ /**
+ * Converts program information to XML.
+ *
+ * @return XML representation of program information.
+ */
+ public Element asXml() {
+ DocumentFactory factory = DocumentFactory.getInstance();
+ Element program = factory.createElement(ELEM_PROGRAM);
+ program.addElement(ELEM_NAME).setText(getName());
+ program.addElement(ELEM_DESCRIPTION).setText(getDescription());
+ program.addElement(ELEM_KEYWORDS).setText(getKeywords());
+ program.addElement(ELEM_CHANNEL).setText(getChannel());
+ Element interval = program.addElement(ELEM_INTERVAL);
+ interval.addElement(ELEM_BEGIN_TIME).setText(
+ getInterval().getBegin().toString());
+ interval.addElement(ELEM_END_TIME).setText(
+ getInterval().getEnd().toString());
+ return program;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * The TV guide.
+ */
+public class TVGuide {
+
+ /**
+ * List of channels.
+ */
+ private List<Channel> _channels;
+
+ /**
+ * Constructs the guide.
+ * @param aChannels Channels of the guide.
+ */
+ public TVGuide(List<Channel> aChannels) {
+ _channels = aChannels;
+ }
+
+ /**
+ * Gets the channels.
+ * @return Channels.
+ */
+ public List<Channel> getChannels() {
+ return Collections.unmodifiableList(_channels);
+ }
+
+ /**
+ * Accepts the visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ aVisitor.visitTvGuide(this);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * TIme at which a program starts or ends.
+ */
+public class Time implements Comparable {
+
+ /**
+ *
+ */
+ private static final int HOURS_PER_DAY = 24;
+
+ /**
+ *
+ */
+ private static final int EARLY_HOUR = 3;
+
+ /**
+ * Number of seconds per minute.
+ */
+ private static final double SECONDS_PER_MINUTE = 60.0;
+
+ /**
+ * Hour of the time.
+ */
+ private int _hour;
+
+ /**
+ * Minute of the hour.
+ */
+ private int _minute;
+
+ /**
+ * Constructs the time.
+ *
+ * @param aHour
+ * Hour.
+ * @param aMinute
+ * Minute.
+ */
+ public Time(int aHour, int aMinute) {
+ _hour = aHour;
+ _minute = aMinute;
+ }
+
+ /**
+ * Gets the hour.
+ *
+ * @return Hour.
+ */
+ public int getHour() {
+ return _hour;
+ }
+
+ /**
+ * Gets te minute.
+ *
+ * @return Minute.
+ */
+ public int getMinute() {
+ return _minute;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ NumberFormat format = new DecimalFormat("00");
+ return format.format(_hour) + ":" + format.format(_minute);
+ }
+
+ /**
+ * Convert time to floating point value. Useful for comparing two times.
+ *
+ * @return Converted value.
+ */
+ float asFloat() {
+ int hour = _hour;
+ // Hack to make sure that programs appearing shortly after midnight are
+ // sorted
+ // after those running during the day.
+ if (hour <= EARLY_HOUR) {
+ hour += HOURS_PER_DAY;
+ }
+ return (float) hour + (float) _minute / (float) SECONDS_PER_MINUTE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof Time)) {
+ return false;
+ }
+ return toString().equals(aObject.toString());
+ }
+
+ /**
+ * Compares based on time.
+ *
+ * @param aObject
+ * Time object to compare to.
+ * @return See {@link Comparable#compareTo(T)}.
+ */
+ public int compareTo(Object aObject) {
+ if (!(aObject instanceof Time)) {
+ throw new IllegalArgumentException("object not an instance of Time");
+ }
+ Time time = (Time) aObject;
+ return new Float(asFloat()).compareTo(new Float(time.asFloat()));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+/**
+ * Time interval.
+ */
+public class TimeInterval {
+
+ /**
+ * Begin time.
+ */
+ private Time _begin;
+
+ /**
+ * End time.
+ */
+ private Time _end;
+
+ /**
+ * Construts the interval.
+ *
+ * @param aBegin
+ * Start time.
+ * @param aEnd
+ * End time.
+ */
+ public TimeInterval(Time aBegin, Time aEnd) {
+ _begin = aBegin;
+ _end = aEnd;
+ }
+
+ /**
+ * Gets the begin time.
+ *
+ * @return Begin time.
+ */
+ public Time getBegin() {
+ return _begin;
+ }
+
+ /**
+ * Gets the end time.
+ *
+ * @return End time.
+ */
+ public Time getEnd() {
+ return _end;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return _begin + " - " + _end;
+ }
+
+ /**
+ * Determines if there is an overlap between the current interval and given
+ * one.
+ *
+ * @param aInterval
+ * Interval to compare with.
+ * @return True iff there is overlap
+ */
+ public boolean overlap(TimeInterval aInterval) {
+
+ if (isUncertain() || aInterval.isUncertain()) {
+ // Optimistic assume there is no overlap if one of the intervals is
+ // uncertain.
+ return false;
+ }
+
+ if (_end.asFloat() <= aInterval._begin.asFloat()
+ || aInterval._end.asFloat() <= _begin.asFloat()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the actual time that the program corresponds to is
+ * uncertain due to the representation of a period of more than 24 hours
+ * using a 24 hour clock.
+ *
+ * @return True iff the interval is uncertain.
+ */
+ boolean isUncertain() {
+ return _begin.asFloat() > _end.asFloat();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)j
+ */
+ @Override
+ public boolean equals(Object aObject) {
+ if (!(aObject instanceof TimeInterval)) {
+ return false;
+ }
+ return aObject.toString().equals(aObject.toString());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _begin.hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.guide;
+
+
+/**
+ * Visitor of the TV guide.
+ */
+public interface Visitor {
+
+ /**
+ * Visits a program.
+ * @param aProgram Program.
+ */
+ void visitProgram(Program aProgram);
+
+ /**
+ * Visits a channel.
+ * @param aChannel Channel.
+ */
+ void visitChannel(Channel aChannel);
+
+ /**
+ * Visits the guide.
+ * @param aGuide Guide.
+ */
+ void visitTvGuide(TVGuide aGuide);
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package contains the object model for the TV guide and the classes for
+searching the TV guide for relevant programs.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action to execute for an interesting program.
+ */
+public class InterestingProgramAction implements ProgramAction {
+
+ /**
+ * Category under which the interesting program is listed.
+ */
+ private String _category;
+
+ /**
+ * Constructs the action.
+ *
+ * @param aCategory
+ * Category of the program. Useful for structuring the output.
+ */
+ public InterestingProgramAction(String aCategory) {
+ _category = aCategory;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramAction#execute(org.wamblee.crawler.kiss.Program,
+ * org.wamblee.crawler.kiss.Report)
+ */
+ public void execute(Program aProgram, ProgramActionExecutor aReport) {
+ if (aProgram.isRecordingPossible()) {
+ aReport.interestingProgram(_category, aProgram);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.mail.MessagingException;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.Action;
+import org.wamblee.crawler.Configuration;
+import org.wamblee.crawler.Crawler;
+import org.wamblee.crawler.Page;
+import org.wamblee.crawler.PageException;
+import org.wamblee.crawler.impl.ConfigurationParser;
+import org.wamblee.crawler.impl.CrawlerImpl;
+import org.wamblee.crawler.kiss.guide.Channel;
+import org.wamblee.crawler.kiss.guide.PrintVisitor;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TVGuide;
+import org.wamblee.crawler.kiss.guide.Time;
+import org.wamblee.crawler.kiss.guide.TimeInterval;
+import org.wamblee.crawler.kiss.notification.NotificationException;
+import org.wamblee.crawler.kiss.notification.Notifier;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.xml.ClasspathUriResolver;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * The KiSS crawler for automatic recording of interesting TV shows.
+ *
+ */
+public class KissCrawler {
+
+ private static final Log LOG = LogFactory.getLog(KissCrawler.class);
+
+ /**
+ * Start URL of the electronic programme guide.
+ */
+ private static final String START_URL = "http://epg.kml.kiss-technology.com/login.php";
+
+ /**
+ * Default socket timeout to use.
+ */
+ private static final int SOCKET_TIMEOUT = 10000;
+
+ /**
+ * Regular expression for matching time interval strings in the retrieved
+ * pages.
+ */
+ private static final String TIME_REGEX = "[^0-9]*([0-9]{2}):([0-9]{2})[^0-9]*([0-9]{2}):([0-9]{2}).*";
+
+ /**
+ * Compiled pattern for the time regular expression.
+ */
+ private Pattern _pattern;
+
+ /**
+ * Runs the KiSS crawler.
+ *
+ * @param aArgs
+ * Arguments: First argument is the crawler configuration file,
+ * and second is the program configuration file.
+ * @throws Exception
+ * In case of problems.
+ */
+ public static void main(String[] aArgs) throws Exception {
+ String crawlerConfig = new File(aArgs[0]).getCanonicalPath();
+ String programConfig = new File(aArgs[1]).getCanonicalPath();
+
+ BeanFactory factory = new StandaloneCrawlerBeanFactory();
+ Notifier notifier = factory.find(Notifier.class);
+ new KissCrawler(START_URL, SOCKET_TIMEOUT, crawlerConfig,
+ programConfig, notifier, new Report());
+ }
+
+ /**
+ * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
+ * EPG guide, filters the guide for interesting programs, tries to record
+ * them, and sends a summary mail to the user.
+ *
+ * @param aCrawlerConfig
+ * Configuration file for the crawler.
+ * @param aProgramConfig
+ * Configuration file describing interesting shows.
+ * @param aNotifier
+ * Object used to send notifications of the results.
+ * @param aReport
+ * Report to use.
+ * @throws IOException
+ * In case of problems reading files.
+ * @throws NotificationException
+ * In case notification fails.
+ * @throws PageException
+ * In case of problems retrieving the TV guide.
+ */
+ public KissCrawler(String aCrawlerConfig, String aProgramConfig,
+ Notifier aNotifier, Report aReport) throws IOException,
+ NotificationException, PageException {
+ this(START_URL, SOCKET_TIMEOUT, aCrawlerConfig, aProgramConfig,
+ aNotifier, aReport);
+ }
+
+ /**
+ * Constructs the crawler. This retrieves the TV guide by crawling the KiSS
+ * EPG guide, filters the guide for interesting programs, tries to record
+ * them, and sends a summary mail to the user.
+ *
+ * @param aStartUrl
+ * Start URL of the electronic programme guide.
+ * @param aSocketTimeout
+ * Socket timeout to use.
+ * @param aCrawlerConfig
+ * Configuration file for the crawler.
+ * @param aProgramConfig
+ * Configuration file describing interesting shows.
+ * @param aNotifier
+ * Object used to send notifications of the results.
+ * @param aReport
+ * Report to use.
+ * @throws IOException
+ * In case of problems reading files.
+ * @throws NotificationException
+ * In case notification fails.
+ * @throws PageException
+ * In case of problems retrieving the TV guide.
+ */
+ public KissCrawler(String aStartUrl, int aSocketTimeout,
+ String aCrawlerConfig, String aProgramConfig, Notifier aNotifier,
+ Report aReport) throws IOException, NotificationException,
+ PageException {
+
+ _pattern = Pattern.compile(TIME_REGEX);
+
+ try {
+ HttpClient client = new HttpClient();
+ // client.getHostConfiguration().setProxy("127.0.0.1", 3128);
+ client.getParams().setParameter("http.socket.timeout",
+ SOCKET_TIMEOUT);
+
+ XslTransformer transformer = new XslTransformer(
+ new ClasspathUriResolver());
+
+ Crawler crawler = createCrawler(aCrawlerConfig, client, transformer);
+ InputStream programConfigFile = new FileInputStream(new File(
+ aProgramConfig));
+ ProgramConfigurationParser parser = new ProgramConfigurationParser();
+ parser.parse(programConfigFile);
+ List<ProgramFilter> programFilters = parser.getFilters();
+
+ try {
+ Page page = getStartPage(aStartUrl, crawler, aReport);
+ TVGuide guide = createGuide(page, aReport);
+ PrintVisitor printer = new PrintVisitor(System.out);
+ guide.accept(printer);
+ processResults(programFilters, guide, aNotifier, aReport);
+ } catch (PageException e) {
+ aReport.addMessage("Problem getting TV guide", e);
+ LOG.info("Problem getting TV guide", e);
+ throw e;
+ }
+ aNotifier.send(aReport.asXml());
+ } finally {
+ System.out.println("Crawler finished");
+ }
+ }
+
+ /**
+ * Records interesting shows.
+ *
+ * @param aProgramCondition
+ * Condition determining which shows are interesting.
+ * @param aGuide
+ * Television guide.
+ * @throws MessagingException
+ * In case of problems sending a summary mail.
+ */
+ private void processResults(List<ProgramFilter> aProgramCondition,
+ TVGuide aGuide, Notifier aNotifier, Report aReport) {
+ ProgramActionExecutor executor = new ProgramActionExecutor(aReport);
+ for (ProgramFilter filter : aProgramCondition) {
+ List<Program> programs = filter.apply(aGuide);
+ ProgramAction action = filter.getAction();
+ for (Program program : programs) {
+ action.execute(program, executor);
+ }
+ }
+ executor.commit();
+
+ }
+
+ /**
+ * Creates the crawler.
+ *
+ * @param aCrawlerConfig
+ * Crawler configuration file.
+ * @param aOs
+ * Logging output stream for the crawler.
+ * @param aClient
+ * HTTP Client to use.
+ * @return Crawler.
+ * @throws FileNotFoundException
+ * In case configuration files cannot be found.
+ */
+ private Crawler createCrawler(String aCrawlerConfig, HttpClient aClient,
+ XslTransformer aTransformer) throws FileNotFoundException {
+ ConfigurationParser parser = new ConfigurationParser(aTransformer);
+ InputStream crawlerConfigFile = new FileInputStream(new File(
+ aCrawlerConfig));
+ Configuration config = parser.parse(crawlerConfigFile);
+ Crawler crawler = new CrawlerImpl(aClient, config);
+ return crawler;
+ }
+
+ /**
+ * Gets the start page of the electronic programme guide. This involves
+ * login and navigation to a suitable start page after logging in.
+ *
+ * @param aStartUrl
+ * URL of the electronic programme guide.
+ * @param aCrawler
+ * Crawler to use.
+ * @param aReport
+ * Report to use.
+ * @return Starting page.
+ */
+ private Page getStartPage(String aStartUrl, Crawler aCrawler, Report aReport)
+ throws PageException {
+ try {
+ Page page = aCrawler.getPage(aStartUrl, new NameValuePair[0]);
+ page = page.getAction("login").execute();
+ Action favorites = page.getAction("channels-favorites");
+ if (favorites == null) {
+ String msg = "Channels favorites action not found on start page";
+ throw new PageException(msg);
+ }
+ return favorites.execute();
+ } catch (PageException e) {
+ String msg = "Could not complete login to electronic programme guide.";
+ throw new PageException(msg, e);
+ }
+ }
+
+ /**
+ * Creates the TV guide by web crawling.
+ *
+ * @param aPage
+ * Starting page.
+ * @param aReport
+ * Report to use.
+ * @return TV guide.
+ * @throws PageException
+ * In case of problem getting the tv guide.
+ */
+ private TVGuide createGuide(Page aPage, Report aReport)
+ throws PageException {
+ LOG.info("Obtaining full TV guide");
+ Action[] actions = aPage.getActions();
+ if (actions.length == 0) {
+ LOG.error("No channels found");
+ throw new PageException("No channels found");
+ }
+ List<Channel> channels = new ArrayList<Channel>();
+ for (Action action : actions) {
+ try {
+ LOG.info("Getting channel info for '" + action.getName() + "'");
+ Action tomorrow = action.execute().getAction("tomorrow");
+ if (tomorrow == null) {
+ throw new PageException("Channel summary page for '"
+ + action.getName()
+ + "' does not contain required information");
+ }
+ Channel channel = createChannel(action.getName(), tomorrow
+ .execute(), aReport);
+ channels.add(channel);
+ if (SystemProperties.isDebugMode()) {
+ break; // Only one channel is crawled.
+ }
+ } catch (PageException e) {
+ aReport.addMessage("Could not create channel information for '"
+ + action.getName() + "'");
+ LOG.error("Could not create channel information for '"
+ + action.getName() + "'", e);
+ }
+ }
+ return new TVGuide(channels);
+ }
+
+ /**
+ * Create channel information for a specific channel.
+ *
+ * @param aChannel
+ * Channel name.
+ * @param aPage
+ * Starting page for the channel.
+ * @return Channel.
+ */
+ private Channel createChannel(String aChannel, Page aPage, Report aReport) {
+ LOG.info("Obtaining program for " + aChannel);
+ Action[] programActions = aPage.getActions();
+ List<Program> programs = new ArrayList<Program>();
+ for (Action action : programActions) {
+ String time = action.getContent().element("time").getText().trim();
+ Matcher matcher = _pattern.matcher(time);
+ if (matcher.matches()) {
+ Time begin = new Time(Integer.parseInt(matcher.group(1)),
+ Integer.parseInt(matcher.group(2)));
+ Time end = new Time(Integer.parseInt(matcher.group(3)), Integer
+ .parseInt(matcher.group(4)));
+ TimeInterval interval = new TimeInterval(begin, end);
+ String description = "";
+ String keywords = "";
+
+ if (!SystemProperties.isNoProgramDetailsRequired()) {
+ Element descriptionElem = action.getContent().element(
+ "description");
+ if (descriptionElem == null) {
+ try {
+ Page programInfo = action.execute();
+ description = programInfo.getContent().element(
+ "description").getText().trim();
+ keywords = programInfo.getContent().element(
+ "keywords").getText().trim();
+ } catch (PageException e) {
+ String msg = "Program details could not be determined for '"
+ + action.getName() + "'";
+ aReport.addMessage(msg, e);
+ LOG.warn(msg, e);
+ }
+ } else {
+ description = descriptionElem.getTextTrim();
+ }
+ }
+ Program program = new Program(aChannel, action.getName(),
+ description, keywords, interval, action);
+
+ LOG.info("Got program " + program);
+ programs.add(program);
+ }
+ }
+ return new Channel(aChannel, programs);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.File;
+
+import org.wamblee.general.ClassLoaderUtils;
+
+/**
+ * Bootstrapper for the kiss crawler which adds all files in the directory
+ * given by the first argument to the classpath.
+ */
+public class KissCrawlerBootstrapper {
+
+ public static void main(String[] aArgs) throws Exception {
+ File libdir = new File(aArgs[0]);
+ if ( !libdir.isDirectory() ) {
+ throw new IllegalArgumentException("'" + aArgs[0] + "' is not a directory.");
+ }
+ ClassLoaderUtils.addJarsInDirectory(libdir);
+ String[] args = new String[2];
+ args[0] = aArgs[1];
+ args[1] = aArgs[2];
+ KissCrawler.main(args);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action configured for a program.
+ */
+public interface ProgramAction {
+
+ /**
+ * Executes the action.
+ *
+ * @param aProgram
+ * Program to execute the action for.
+ * @param aExecutor
+ * Executor to use.
+ */
+ void execute(Program aProgram, ProgramActionExecutor aExecutor);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TimeInterval;
+import org.wamblee.crawler.kiss.guide.Program.RecordingResult;
+
+/**
+ * Provides execution of actions for programs. Actions use this class to tell
+ * the executor what to do. The executor then decides on exactly what to do and
+ * in what order and makes decisions in case of conflicts.
+ */
+public class ProgramActionExecutor {
+
+ private static final Log LOG = LogFactory
+ .getLog(ProgramActionExecutor.class);
+
+ /**
+ * Map of priority to set of programs.
+ */
+ private Map<Integer, Set<Program>> _showsToRecord;
+
+ /**
+ * Report to use.
+ */
+ private Report _report;
+
+ /**
+ * Constructs the program action executor.
+ *
+ * @param aReport Report to use.
+ */
+ public ProgramActionExecutor(Report aReport) {
+ _showsToRecord = new TreeMap<Integer, Set<Program>>();
+ _report = aReport;
+ }
+
+ /**
+ * Called by an action to indicate the desire to record a program.
+ *
+ * @param aPriority
+ * Priority of the program. Used to resolve conflicts.
+ * @param aProgram
+ * Program to record.
+ */
+ public void recordProgram(int aPriority, Program aProgram) {
+ LOG.info("priority = " + aPriority + ", program: " + aProgram);
+ // Putting -priority into the set makes sure that iteration order
+ // over the priorities will go from higher priority to lower priority.
+ Set<Program> programs = _showsToRecord.get(-aPriority);
+ if (programs == null) {
+ programs = new TreeSet<Program>(new Program.TimeComparator());
+ _showsToRecord.put(-aPriority, programs);
+ }
+ programs.add(aProgram);
+ }
+
+ /**
+ * Called by an action to indicate that a program is interesting.
+ *
+ * @param aCategory
+ * Category of the program.
+ * @param aProgram
+ * Program.
+ */
+ public void interestingProgram(String aCategory, Program aProgram) {
+ LOG.info("category = '" + aCategory + "', program: " + aProgram);
+ _report.interestingProgram(aCategory, aProgram);
+ }
+
+ /**
+ * Makes sure that the actions are performed.
+ */
+ public void commit() {
+ Set<TimeInterval> previouslyRecorded = new HashSet<TimeInterval>();
+ for (Integer priority : _showsToRecord.keySet()) {
+ for (Program program : _showsToRecord.get(priority)) {
+ TimeInterval interval = program.getInterval();
+ if (recordingConflictExists(previouslyRecorded, interval)) {
+ _report.setRecordingResult(RecordingResult.CONFLICT, program);
+ } else {
+ RecordingResult result = program.record();
+ _report.setRecordingResult(result, program);
+ previouslyRecorded.add(interval);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks an interval for overlap with a previously recorded program.
+ *
+ * @param aPreviouslyRecorded
+ * Previously recorded programs.
+ * @param aInterval
+ * Interval.
+ * @return True iff there is a recording conflict.
+ */
+ private boolean recordingConflictExists(
+ Set<TimeInterval> aPreviouslyRecorded, TimeInterval aInterval) {
+ for (TimeInterval recordedInterval : aPreviouslyRecorded) {
+ if (aInterval.overlap(recordedInterval)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.wamblee.conditions.AndCondition;
+import org.wamblee.conditions.Condition;
+import org.wamblee.conditions.PropertyRegexCondition;
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Parse the configuration of desired programs.
+ */
+class ProgramConfigurationParser {
+ private static final int DEFAULT_PRIORITY = 1;
+
+ // Configuration of interesting programs.
+
+ private static final String ELEM_PROGRAM = "program";
+
+ private static final String ELEM_PRIORITY = "priority";
+
+ private static final String ELEM_PATTERN = "match";
+
+ private static final String ELEM_ACTION = "action";
+
+ private static final String ELEM_CATEGORY = "category";
+
+ private static final String ACTION_NOTIFY = "notify";
+
+ private List<ProgramFilter> _filters;
+
+ ProgramConfigurationParser() {
+ _filters = null;
+ }
+
+ /**
+ * Parses the condition used to match the desired programs.
+ *
+ * @param aStream
+ * Input stream to parse from.
+ * @return Condition.
+ */
+ void parse(InputStream aStream) {
+ List<ProgramFilter> filters = new ArrayList<ProgramFilter>();
+ try {
+ SAXReader reader = new SAXReader();
+ Document document = reader.read(aStream);
+
+ Element root = document.getRootElement();
+
+ for (Iterator i = root.elementIterator(ELEM_PROGRAM); i.hasNext();) {
+ Element program = (Element) i.next();
+
+ Element categoryElem = program.element(ELEM_CATEGORY);
+ String category = "";
+ if (categoryElem != null) {
+ category = categoryElem.getText().trim();
+ }
+
+ Element actionElem = program.element(ELEM_ACTION);
+ int priority = DEFAULT_PRIORITY;
+ String priorityString = program.elementTextTrim(ELEM_PRIORITY);
+ if ( priorityString != null ) {
+ priority = Integer.valueOf(priorityString);
+ }
+ ProgramAction action = new RecordProgramAction(priority);
+ if (actionElem != null) {
+ if (actionElem.getText().equals(ACTION_NOTIFY)) {
+ action = new InterestingProgramAction(category);
+ }
+ }
+
+ List<Condition<Program>> regexConditions = new ArrayList<Condition<Program>>();
+ for (Iterator j = program.elementIterator(ELEM_PATTERN); j
+ .hasNext();) {
+ Element patternElem = (Element) j.next();
+ String fieldName = "name";
+ Attribute fieldAttribute = patternElem.attribute("field");
+ if (fieldAttribute != null) {
+ fieldName = fieldAttribute.getText();
+ }
+ String pattern = ".*(" + patternElem.getText() + ").*";
+ regexConditions.add(new PropertyRegexCondition<Program>(
+ fieldName, pattern, true));
+ }
+ Condition<Program> condition = new AndCondition<Program>(
+ regexConditions);
+ filters.add(new ProgramFilter(condition, action));
+ }
+ _filters = filters;
+ } catch (DocumentException e) {
+ throw new RuntimeException("Error parsing program configuraiton", e);
+ }
+ }
+
+ /**
+ * Returns the list of program filters.
+ *
+ * @return Filter list.
+ */
+ public List<ProgramFilter> getFilters() {
+ return _filters;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+import org.wamblee.crawler.kiss.guide.MatchVisitor;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.TVGuide;
+
+/**
+ * Obtains a list of interesting programs from a TV guide and decides what to do
+ * with them.
+ */
+public class ProgramFilter {
+
+ private Condition<Program> _condition;
+
+ private ProgramAction _action;
+
+ /**
+ * Constructs the program filter.
+ * @param aCondition Condition used to find interesting programs.
+ * @param aAction Corresponding action to execute for matching programs.
+ */
+ public ProgramFilter(Condition<Program> aCondition, ProgramAction aAction) {
+ _condition = aCondition;
+ _action = aAction;
+ }
+
+ /**
+ * Gets the action.
+ * @return Action.
+ */
+ public ProgramAction getAction() {
+ return _action;
+ }
+
+ /**
+ * Applies the filter to a TV guide.
+ * @param aGuide TV guide.
+ * @return List of matching programs.
+ */
+ public List<Program> apply(TVGuide aGuide) {
+ MatchVisitor matcher = new MatchVisitor(_condition);
+ aGuide.accept(matcher);
+ return matcher.getMatches();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.crawler.kiss.guide.Program;
+
+/**
+ * Represents an action to record a program.
+ */
+public class RecordProgramAction implements ProgramAction {
+
+ private int _priority;
+
+ /**
+ * Constructs the action.
+ * @param aPriority Priority of the recording action. Higher values have higher
+ * priority.
+ */
+ public RecordProgramAction(int aPriority) {
+ _priority = aPriority;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramAction#execute(org.wamblee.crawler.kiss.Program,
+ * org.wamblee.crawler.kiss.Report)
+ */
+ public void execute(Program aProgram, ProgramActionExecutor aReport) {
+ aReport.recordProgram(_priority, aProgram);
+ }
+
+}
--- /dev/null
+/*
+ *
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dom4j.DocumentFactory;
+import org.dom4j.Element;
+import org.wamblee.crawler.kiss.guide.Program;
+import org.wamblee.crawler.kiss.guide.Program.RecordingResult;
+
+/**
+ * Represents a report on the actions of the crawler.
+ */
+public class Report {
+
+ private static final Log LOG = LogFactory
+ .getLog(Report.class);
+
+ /**
+ * A map of category name to a set of program. Useful for displaying the
+ * output of possibly interesting programs on a per category basis.
+ */
+ private Map<String, Set<Program>> _interestingShows;
+
+ /**
+ * Map or recording result to a set of programs.
+ */
+ private EnumMap<RecordingResult, Set<Program>> _recordings;
+
+ /**
+ * Messages generated while doing all the work.
+ */
+ private List<String> _messages;
+
+ /**
+ * Constructs the report.
+ *
+ */
+ public Report() {
+ _interestingShows = new TreeMap<String, Set<Program>>();
+ _recordings = new EnumMap<RecordingResult, Set<Program>>(
+ RecordingResult.class);
+ for (RecordingResult result : RecordingResult.values()) {
+ _recordings.put(result, new TreeSet<Program>(
+ new Program.TimeComparator()));
+ }
+ _messages = new ArrayList<String>();
+ }
+
+ /**
+ * Adds a message.
+ *
+ * @param aMessage
+ * Message to add.
+ */
+ public void addMessage(String aMessage) {
+ _messages.add(aMessage);
+ }
+
+ /**
+ * Adds a message.
+ *
+ * @param aMessage
+ * Message to add.
+ * @param aException Exception that caused the problem.
+ */
+ public void addMessage(String aMessage, Exception aException) {
+ String msg = aMessage;
+ for (Throwable e = aException; e != null; e = e.getCause()) {
+ msg += ": " + e.getMessage();
+ }
+ addMessage(msg);
+ }
+
+ /**
+ * Called to indicate that a program is interesting.
+ *
+ * @param aCategory
+ * Category of the program.
+ * @param aProgram
+ * Program.
+ */
+ public void interestingProgram(String aCategory, Program aProgram) {
+ LOG.info("category = '" + aCategory + "', program: " + aProgram);
+ Set<Program> programs = _interestingShows.get(aCategory);
+ if (programs == null) {
+ programs = new TreeSet<Program>(new Program.TimeComparator());
+ _interestingShows.put(aCategory, programs);
+ }
+ programs.add(aProgram);
+ }
+
+ /**
+ * Called to specify the result of recording a program.
+ * @param aResult Result.
+ * @param aProgram Program.
+ */
+ public void setRecordingResult(RecordingResult aResult, Program aProgram) {
+ _recordings.get(aResult).add(aProgram);
+ }
+
+
+ /**
+ * Get report as XML.
+ *
+ * @return XML report
+ */
+ public Element asXml() {
+ DocumentFactory factory = DocumentFactory.getInstance();
+ Element report = factory.createElement("report");
+
+ if (_messages.size() > 0) {
+ Element messages = report.addElement("messages");
+ for (String message : _messages) {
+ messages.addElement("message").setText(message);
+ }
+ }
+
+ Set<Program> reportedPrograms = new HashSet<Program>();
+
+ for (RecordingResult result : RecordingResult.values()) {
+ if (_recordings.get(result).size() > 0) {
+ Element recordingResult = report.addElement("recorded")
+ .addAttribute("result", result.toString());
+
+ for (Program program : _recordings.get(result)) {
+ recordingResult.add(program.asXml());
+ reportedPrograms.add(program);
+ }
+ }
+ }
+
+ if (_interestingShows.size() > 0) {
+ Element interesting = report.addElement("interesting");
+ for (String category : _interestingShows.keySet()) {
+ Element categoryElem = interesting;
+ if (category.length() > 0) {
+ categoryElem = interesting.addElement("category");
+ categoryElem.addAttribute("name", category);
+ }
+ for (Program program : _interestingShows.get(category)) {
+ if (!reportedPrograms.contains(program)) {
+ categoryElem.add(program.asXml());
+ } else {
+ LOG.info("Category '" + category + "', program "
+ + program + " already reported");
+ }
+ }
+ if (categoryElem.elements().size() == 0) {
+ // Remove empty category element.
+ LOG
+ .info("Removing element for category '" + category
+ + "'");
+ interesting.remove(categoryElem);
+ }
+ }
+
+ }
+
+ return report;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+import org.wamblee.general.SpringBeanFactory;
+
+/**
+ * Bean factory used for the standalone crawler application.
+ */
+public class StandaloneCrawlerBeanFactory extends SpringBeanFactory {
+
+ private static final String LOCATOR = "crawler-standalone.xml";
+ private static final String FACTORY_NAME = "crawlerStandalone";
+
+ /**
+ * Constructs the factory.
+ *
+ */
+ public StandaloneCrawlerBeanFactory() {
+ super(LOCATOR, FACTORY_NAME);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.main;
+
+/**
+ * Access to system properties for the crawler.
+ */
+public final class SystemProperties {
+
+ private static final String DEBUG_PROPERTY = "kiss.debug";
+
+ private static final String NO_PROGRAM_DETAILS = "kiss.nodetails";
+
+ private static final String DISABLE_RECORD = "kiss.norecord";
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private SystemProperties() {
+ // Empty.
+ }
+
+ /**
+ * Determines if the system is run in debug mode. When in debug mode, less
+ * extensive crawling is done.
+ *
+ * @return True iff we are running in debug mode.
+ */
+ public static boolean isDebugMode() {
+ return System.getProperties().getProperty(DEBUG_PROPERTY) != null;
+ }
+
+ /**
+ * Determines if no program details are required.
+ *
+ * @return True iff no program details are required.
+ */
+ public static boolean isNoProgramDetailsRequired() {
+ return System.getProperties().getProperty(NO_PROGRAM_DETAILS) != null;
+ }
+
+ /**
+ * Determines if recording is disabled.
+ *
+ * @return True iff no recording should be done.
+ */
+ public static boolean isRecordDisabled() {
+ return System.getProperties().getProperty(DISABLE_RECORD) != null;
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package contains the crawling logic of the KiSS EPG site as well
+as the configuration classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.HtmlEmail;
+import org.dom4j.Element;
+import org.wamblee.xml.XslTransformer;
+
+/**
+ * A notifier that uses SMTP to notify users by mail.
+ *
+ */
+public class MailNotifier implements Notifier {
+
+ private String _from;
+
+ private String _to;
+
+ private String _subject;
+
+ private String _htmlXslt;
+
+ private String _textXslt;
+
+ private MailServer _server;
+
+ private XslTransformer _transformer;
+
+ /**
+ * Constructs the notifier.
+ *
+ * @param aFrom
+ * Sender mail address to use.
+ * @param aTo
+ * Recipient mail address to use.
+ * @param aSubject
+ * Subject to use in the email.
+ * @param aHtmlXslt
+ * XSLT file to transform the report into HTML.
+ * @param aTextXslt
+ * XSLT file to transform the report into text.
+ * @param aServer
+ * Mail server to use.
+ * @param aTransformer Transformer to use.
+ */
+ public MailNotifier(String aFrom, String aTo, String aSubject,
+ String aHtmlXslt, String aTextXslt, MailServer aServer, XslTransformer aTransformer) {
+ _from = aFrom;
+ _to = aTo;
+ _subject = aSubject;
+ _htmlXslt = aHtmlXslt;
+ _textXslt = aTextXslt;
+ _server = aServer;
+ _transformer = aTransformer;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.Notifier#send(org.dom4j.Element)
+ */
+ public void send(Element aReport) throws NotificationException {
+ HtmlEmail mail = new HtmlEmail();
+ try {
+ mail.setFrom(_from);
+ mail
+ .setTo(Arrays
+ .asList(new InternetAddress[] { new InternetAddress(
+ _to) }));
+ mail.setSentDate(new Date());
+ mail.setSubject(_subject);
+
+ String htmlText = transformReport(aReport, _htmlXslt);
+ String plainText = transformReport(aReport, _textXslt);
+
+ mail.setHtmlMsg(htmlText);
+ mail.setTextMsg(plainText);
+
+ _server.send(mail);
+ } catch (EmailException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (TransformerException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new NotificationException(e.getMessage(), e);
+ } catch (AddressException e) {
+ throw new NotificationException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Transforms a report into a destination format.
+ *
+ * @param aReport
+ * Report to transform
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed result.
+ * @throws IOException
+ * In case of IO problems.
+ * @throws TransformerException
+ * In case of problems transforming.
+ */
+ private String transformReport(Element aReport, String aXslt)
+ throws IOException, TransformerException {
+ String reportXmlText = aReport.asXML();
+ return _transformer.textTransform(reportXmlText.getBytes(), _transformer.resolve(aXslt));
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.notification.Notifier#asHtml(org.dom4j.Element)
+ */
+ public String asHtml(Element aReport) throws IOException, TransformerException {
+ return transformReport(aReport, _htmlXslt);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.notification.Notifier#asText(org.dom4j.Element)
+ */
+ public String asText(Element aReport) throws IOException, TransformerException {
+ return transformReport(aReport, _textXslt);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+
+/**
+ * Mail server.
+ */
+public class MailServer {
+
+ private String _host;
+
+ private int _port;
+
+ private String _username;
+
+ private String _password;
+
+ /**
+ * Constructs the mail server.
+ *
+ * @param aHost
+ * Host name of the SMTP server.
+ * @param aPort
+ * Port name of the SMTP server.
+ * @param aUsername
+ * Username to use for authentication or null if no
+ * authentication is required.
+ * @param aPassword
+ * Password to use for authentication or null if no authenticatio
+ * is required.
+ */
+ public MailServer(String aHost, int aPort, String aUsername,
+ String aPassword) {
+ _host = aHost;
+ _port = aPort;
+ _username = aUsername;
+ _password = aPassword;
+ }
+
+ /**
+ * Sends an e-mail.
+ *
+ * @param aMail
+ * Mail to send.
+ * @throws EmailException
+ * In case of problems sending the mail.
+ */
+ public void send(Email aMail) throws EmailException {
+ Properties props = new Properties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.host", _host);
+ props.put("mail.smtp.port", "" + _port);
+
+ Session mailSession = Session.getInstance(props,
+ new UsernamePasswordAuthenticator(_username, _password));
+ aMail.setMailSession(mailSession);
+ aMail.send();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+/**
+ * Notification exception thrown in case of problems sending a notification to a
+ * user.
+ *
+ */
+public class NotificationException extends Exception {
+
+ /**
+ * Constructs the notification.
+ *
+ * @param aMsg
+ * Message.
+ */
+ public NotificationException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the notification.
+ *
+ * @param aMsg
+ * Message.
+ * @param aCause
+ * Cause.
+ */
+ public NotificationException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import java.io.IOException;
+
+import javax.xml.transform.TransformerException;
+
+import org.dom4j.Element;
+
+/**
+ * Object used to send notifications about the actions of the crawler.
+ *
+ */
+public interface Notifier {
+
+ /**
+ * Sends a notification.
+ *
+ * @param aReport
+ * Report to send.
+ */
+ void send(Element aReport) throws NotificationException;
+
+ /**
+ * Converts the report to html.
+ * @param aReport Report to convert.
+ * @return
+ */
+ String asHtml(Element aReport) throws IOException, TransformerException;
+
+ /**
+ * Converts the report to text.
+ * @param aReport Report to convert.
+ * @return
+ */
+ String asText(Element aReport) throws IOException, TransformerException;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.notification;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+
+/**
+ * Authenticator to supply username and password to the mail server (if needed).
+ *
+ */
+public class UsernamePasswordAuthenticator extends Authenticator {
+
+ private String _username;
+
+ private String _password;
+
+ /**
+ * Constructs the authenticator.
+ *
+ * @param aUsername
+ * User name.
+ * @param aPassword
+ * Password.
+ */
+ public UsernamePasswordAuthenticator(String aUsername, String aPassword) {
+ _username = aUsername;
+ _password = aPassword;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.mail.Authenticator#getPasswordAuthentication()
+ */
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ if (_username == null) {
+ return null;
+ }
+ return new PasswordAuthentication(_username, _password);
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+Contains the classes for notifying users of the results of crawling.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides a crawler for the KiSS electronic program guide.
+It provides automatic recording of programs that satisfy criteria specified
+by the user.
+
+The following packages are defined:
+<ul>
+ <li> {@link org.wamblee.crawler.kiss.main}: Contains the crawling functionality and
+ configuration classes.
+ </li>
+ <li> {@link org.wamblee.crawler.kiss.guide}: Contains the TV guide object model and the
+ classes for searching relevant programs in the guide.
+ </li>
+ <li> {@link org.wamblee.crawler.kiss.notification}: Contains the classes for
+ notification.
+ </li>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-overview">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <xsl:when test="contains(text(), 'Right now')">
+ <xsl:text>right-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Evening')">
+ <xsl:text>evening</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Afternoon')">
+ <xsl:text>afternoon</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Noon')">
+ <xsl:text>noon</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Morning')">
+ <xsl:text>morning</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Tomorrow')">
+ <xsl:text>tomorrow</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:value-of select="$type"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:value-of select="$type"/>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml" version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates
+ select="//xhtml:table[3]//xhtml:tr[xhtml:td[not(contains(@class, 'listCell'))]]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:tr">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[3]//xhtml:a"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="xhtml:td[3]//xhtml:a/@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:apply-templates select=".//xhtml:script"/>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+ <xsl:template match="xhtml:script">
+ <xsl:variable name="script">
+ <xsl:value-of select="."/>
+ </xsl:variable>
+ <xsl:variable name="description">
+ <xsl:value-of
+ select="substring-before(substring-after($script, '<br>'), '"]')"/>
+ </xsl:variable>
+ <description>
+ <xsl:value-of select="$description"/>
+ </description>
+
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates select="//xhtml:tr[xhtml:td[not(contains(@class, 'listCell'))]]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:tr">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[2]//xhtml:a"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="xhtml:td[2]//xhtml:a/@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><channel-right-now><action name=">>" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvlist.php?reload=1&channel=6&tz=1&progid=0&page=0&day=1&back=$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time> </time></action>
+
+ <action name="Wintertijd" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362161&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 23:55 - 00:40 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhalingen" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772362162&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+00:50 - 06:15 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395832&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:45 - 06:59 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395833&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+06:59 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395834&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 07:00 - 07:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395835&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:10 - 07:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395836&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:30 - 07:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395837&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+07:40 - 08:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395838&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:00 - 08:10 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395839&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:10 - 08:30 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395840&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 08:30 - 08:40 - </time></action>
+
+ <action name="Goedemorgen Nederland" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395841&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+08:40 - 09:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395842&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:00 - 09:10 - </time></action>
+
+ <action name="Nederland in beweging" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395843&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:10 - 09:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395844&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:30 - 09:55 - </time></action>
+
+ <action name="Schoondochter gezocht" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395845&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+09:55 - 10:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395846&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+ 10:50 - 11:15 - </time></action>
+
+ <action name="Appeltje voor de dorst" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395847&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>
+11:15 - 12:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395848&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:00 - 12:10 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395849&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:10 - 12:35 - </time></action>
+
+ <action name="Voor alle fans: Drukwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395850&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:35 - 12:57 - </time></action>
+
+ <action name="Trekking Lingo" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395851&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>12:57 - 13:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395852&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:00 - 13:10 - </time></action>
+
+ <action name="NOS-Sportjournaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395853&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:10 - 13:20 - </time></action>
+
+ <action name="Buitenhof" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395854&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>13:20 - 14:15 - </time></action>
+
+ <action name="Hoge bomen in de misdaad" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395855&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:15 - 14:55 - </time></action>
+
+ <action name="AVRO Dierenpark" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395856&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>14:55 - 15:20 - </time></action>
+
+ <action name="Kruispunt" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395857&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>15:20 - 16:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395858&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:00 - 16:05 - </time></action>
+
+ <action name="Helden van nu: Vrijwilligers in de gezondheidszorg" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395859&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:05 - 16:30 - </time></action>
+
+ <action name="Leven met verlies" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395860&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>16:30 - 17:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395861&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:00 - 17:10 - </time></action>
+
+ <action name="Schepper & co" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395862&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:10 - 17:35 - </time></action>
+
+ <action name="MAX & Catherine" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395863&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>17:35 - 18:30 - </time></action>
+
+ <action name="That's the question" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395864&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:30 - 18:55 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395865&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>18:55 - 19:25 - </time></action>
+
+ <action name="Ingang Oost" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395866&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>19:25 - 20:00 - </time></action>
+
+ <action name="NOS-Journaal" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395867&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:00 - 20:30 - </time></action>
+
+ <action name="Netwerk" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395868&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>20:30 - 21:05 - </time></action>
+
+ <action name="Memories" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395869&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>21:05 - 22:05 - </time></action>
+
+ <action name="Keyzer & De Boer Advocaten" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395870&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:05 - 22:55 - </time></action>
+
+ <action name="NCRV Dokument: Een familie van vaders" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395871&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>22:55 - 23:50 - </time></action>
+
+ <action name="Blauw bloed" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395872&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>23:50 - 00:20 - </time></action>
+
+ <action name="Man bijt hond" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395873&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:20 - 00:50 - </time></action>
+
+ <action name="Nacht-tv: Netwerk herhaling" type="program-info" reference="http://epg.kml.kiss-technology.com/tvhtml/tvinfo.php?id=1772395874&back=$tvhtml$tvlist.php(channel)6~day)0~page)0~tz)1~progid)0~sel)0~back)$tvhtml$tvstation.php(mode)3~station)6~tz)1~sel)0~back)$tvhtml$tv.php(mode)3~page)0~station)0~tz)1~sel)0~back)$tvhtml$tvstart.php(tz)1"><time>00:50 - 06:45 - </time></action>
+
+
+BackHomeLogout</channel-right-now>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="channel-right-now">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a[ count(following::xhtml:a) >= 3]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>program-info</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:element name="time">
+ <xsl:value-of select="preceding-sibling::text()[1]"/>
+ </xsl:element>
+ </xsl:element>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="favorite-channels">
+ <xsl:apply-templates select="//xhtml:td[contains(@class, 'listCell') and position() = 2]"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:td">
+ <action name="{.//xhtml:b}" type="channel-overview"
+ reference="{xhtml:a/@href}"/>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="favorite-channels">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a[not(contains(@href, 'reload')) and text() != 'Back' and text() != 'Home' and text() != 'Logout' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="text()"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type"><xsl:text>channel-overview</xsl:text></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="crawlerStandalone"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.crawler.properties.xml</value>
+ <value>org.wamblee.crawler.notification.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
+
+ <!-- =====================================================
+ By default, simply copy everything
+ ===================================================== -->
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\r
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"\r
+ version="1.0">\r
+ \r
+ <xsl:output method="xml"/>\r
+ <xsl:strip-space elements="*"/>\r
+ \r
+ <!-- =====================================================\r
+ Copying template.\r
+ ===================================================== -->\r
+ <xsl:template match="@*|node()" mode="copy">\r
+ <xsl:copy>\r
+ <xsl:apply-templates select="@*|node()" mode="copy"/>\r
+ </xsl:copy>\r
+ </xsl:template>\r
+ \r
+ <xsl:template match="/">\r
+ <xsl:element name="login">\r
+ <xsl:apply-templates select="//xhtml:table[@class = 'tvstart']"/>\r
+ </xsl:element>\r
+ </xsl:template>\r
+ \r
+ <xsl:template match="xhtml:table">\r
+ <action type="channels-whats-on-now" name="channels-whats-on-now" reference="{xhtml:tr[3]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="channels-whats-on" name="channels-whats-on" reference="{xhtml:tr[5]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="channels-favorites" name="channels-favorites" reference="{xhtml:tr[7]/xhtml:td[1]//@href}"/>\r
+ \r
+ <action type="shows-whats-on" name="shows-whats-on" reference="{xhtml:tr[3]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-search" name="shows-search" reference="{xhtml:tr[5]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-favorites" name="shows-favorites" reference="{xhtml:tr[7]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="shows-add-favorite" name="shows-add-favorite" reference="{xhtml:tr[9]/xhtml:td[3]//@href}"/>\r
+ \r
+ <action type="movies-whats-on" name="movies-whats-on" reference="{xhtml:tr[3]/xhtml:td[5]//@href}"/>\r
+ \r
+ <action type="sports-whats-on" name="sports-whats-on" reference="{xhtml:tr[9]/xhtml:td[5]//@href}"/>\r
+ \r
+ \r
+ </xsl:template>\r
+ \r
+ <xsl:template match="xhtml:a">\r
+ <xsl:variable name="type">\r
+ <xsl:choose>\r
+ <!-- Everything in the Favorite Channels section --> \r
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and \r
+ contains(text(),\r
+ 'Channels')]\r
+ and following::node()[contains(text(), 'Favorite') and\r
+ contains(text(), 'Shows')]">\r
+ <favorite-channels>\r
+ <xsl:choose>\r
+ <xsl:when test="contains(text(), \r
+ 's on now?')">\r
+ <xsl:text>channels-whats-on-now</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 's on?')">\r
+ <xsl:text>channels-whats-on</xsl:text> \r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Favorites')">\r
+ <xsl:text>channels-favorites</xsl:text> \r
+ </xsl:when>\r
+ </xsl:choose>\r
+ </favorite-channels>\r
+ </xsl:when>\r
+ \r
+ <!-- Everything in the Favorite Shows section --> \r
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and \r
+ contains(text(),\r
+ 'Shows')]\r
+ and following::node()[contains(text(), 'Movies')]">\r
+ <xsl:choose>\r
+ <xsl:when test="contains(text(), 's on?')">\r
+ <xsl:text>shows-whats-on</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Search a show')">\r
+ <xsl:text>shows-search</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Favorites')">\r
+ <xsl:text>shows-favorites</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Add a favorite')">\r
+ <xsl:text>shows-add-favorite</xsl:text>\r
+ </xsl:when> \r
+ </xsl:choose> \r
+ </xsl:when>\r
+ \r
+ <!-- The Movies section -->\r
+ <xsl:when test="preceding::node()[contains(text(), 'Movies')]\r
+ and following::node()[contains(text(), 'Sports')] \r
+ and contains(text(), 's on?')">\r
+ <xsl:text>movies-whats-on</xsl:text> \r
+ </xsl:when>\r
+ \r
+ <!-- Everything in the sports section --> \r
+ \r
+ <xsl:when test="preceding::node()[contains(text(), 'Sports')] \r
+ and contains(text(), 's on?')">\r
+ <xsl:text>sports-whats-on</xsl:text> \r
+ </xsl:when>\r
+ \r
+ <xsl:when test="contains(text(), 'Logout')">\r
+ <xsl:text>logout</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'Manual')">\r
+ <xsl:text>manual-recording</xsl:text>\r
+ </xsl:when>\r
+ <xsl:when test="contains(text(), 'View')">\r
+ <xsl:text>view-recordings</xsl:text>\r
+ </xsl:when>\r
+ <xsl:otherwise>\r
+ <xsl:text>unknown</xsl:text>\r
+ <xsl:value-of select="text()"/>\r
+ </xsl:otherwise>\r
+ </xsl:choose>\r
+ </xsl:variable>\r
+ <xsl:element name="action">\r
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>\r
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>\r
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>\r
+ </xsl:element>\r
+ </xsl:template>\r
+ \r
+</xsl:stylesheet>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="login">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <!-- Everything in the Favorite Channels section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and
+ contains(text(),
+ 'Channels')]
+ and following::node()[contains(text(), 'Favorite') and
+ contains(text(), 'Shows')]">
+
+ <xsl:choose>
+ <xsl:when test="contains(text(),
+ 's on now?')">
+ <xsl:text>channels-whats-on-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 's on?')">
+ <xsl:text>channels-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Favorites')">
+ <xsl:text>channels-favorites</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- Everything in the Favorite Shows section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Favorite') and
+ contains(text(),
+ 'Shows')]
+ and following::node()[contains(text(), 'Movies')]">
+ <xsl:choose>
+ <xsl:when test="contains(text(), 's on?')">
+ <xsl:text>shows-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Search a show')">
+ <xsl:text>shows-search</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Favorites')">
+ <xsl:text>shows-favorites</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Add a favorite')">
+ <xsl:text>shows-add-favorite</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- The Movies section -->
+ <xsl:when test="preceding::node()[contains(text(), 'Movies')]
+ and following::node()[contains(text(), 'Sports')]
+ and contains(text(), 's on?')">
+ <xsl:text>movies-whats-on</xsl:text>
+ </xsl:when>
+
+ <!-- Everything in the sports section -->
+
+ <xsl:when test="preceding::node()[contains(text(), 'Sports')]
+ and contains(text(), 's on?')">
+ <xsl:text>sports-whats-on</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="contains(text(), 'Logout')">
+ <xsl:text>logout</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Manual')">
+ <xsl:text>manual-recording</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'View')">
+ <xsl:text>view-recordings</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>unknown</xsl:text>
+ <xsl:value-of select="text()"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="login">
+ <xsl:apply-templates select="//xhtml:a"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:a">
+ <xsl:variable name="type">
+ <xsl:choose>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 's on now?')">
+ <xsl:text>channels-whats-on-now</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 's on?')">
+ <xsl:text>channels-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Channels'] and contains(text(), 'Favorites')">
+ <xsl:text>channels-favorites</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 's on?')">
+ <xsl:text>shows-whats-on</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Search a show')">
+ <xsl:text>shows-search</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Favorites')">
+ <xsl:text>shows-favorites</xsl:text>
+ </xsl:when>
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Favorite Shows'] and contains(text(), 'Add a favorite')">
+ <xsl:text>shows-add-favorite</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Movies'] and contains(text(), 's on?')">
+ <xsl:text>movies-whats-on</xsl:text>
+ </xsl:when>
+
+
+ <xsl:when test="preceding::xhtml:h2[position() = 1 and text() = 'Sports'] and contains(text(), 's on?')">
+ <xsl:text>sports-whats-on</xsl:text>
+ </xsl:when>
+
+ <xsl:when test="contains(text(), 'Logout')">
+ <xsl:text>logout</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'Manual')">
+ <xsl:text>manual-recording</xsl:text>
+ </xsl:when>
+ <xsl:when test="contains(text(), 'View')">
+ <xsl:text>view-recordings</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ unknown
+ <xsl:value-of select="text()"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:element name="action">
+ <xsl:attribute name="name"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="type"><xsl:value-of select="$type"/></xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@href"/></xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="loginform">
+ <xsl:apply-templates select="//xhtml:form"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="//xhtml:form">
+ <xsl:element name="action">
+ <xsl:attribute name="name">login</xsl:attribute>
+ <xsl:attribute name="reference"><xsl:value-of select="@action"/></xsl:attribute>
+ <xsl:attribute name="type">login</xsl:attribute>
+ <xsl:apply-templates select="//xhtml:input[@type='hidden']"/>
+ </xsl:element>
+
+ </xsl:template>
+
+ <xsl:template match="xhtml:input">
+ <xsl:element name="param">
+ <xsl:attribute name="name">
+ <xsl:value-of select="@name"/>
+ </xsl:attribute>
+ <xsl:attribute name="value">
+ <xsl:value-of select="@value"/>
+ </xsl:attribute>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <!-- Mail server configuration -->
+ <bean id="org.wamblee.crawler.kiss.notification.MailServer"
+ class="org.wamblee.crawler.kiss.notification.MailServer">
+ <constructor-arg><value>${org.wamblee.crawler.smtp.host}</value></constructor-arg>
+ <constructor-arg><value type="int">${org.wamblee.crawler.smtp.port}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.smtp.username}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.smtp.password}</value></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.xml.ClasspathUriResolver"
+ class="org.wamblee.xml.ClasspathUriResolver">
+ </bean>
+
+ <bean id="org.wamblee.xml.XslTransformer"
+ class="org.wamblee.xml.XslTransformer">
+ <constructor-arg><ref local="org.wamblee.xml.ClasspathUriResolver"/></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.crawler.kiss.notification.Notifier"
+ class="org.wamblee.crawler.kiss.notification.MailNotifier">
+ <constructor-arg><value>${org.wamblee.crawler.notification.from}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.notification.to}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.notification.subject}</value></constructor-arg>
+ <constructor-arg><value>reportToHtml.xsl</value></constructor-arg>
+ <constructor-arg><value>reportToText.xsl</value></constructor-arg>
+ <constructor-arg><ref local="org.wamblee.crawler.kiss.notification.MailServer"/></constructor-arg>
+ <constructor-arg><ref local="org.wamblee.xml.XslTransformer"/></constructor-arg>
+ </bean>
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="propertyBean"
+ class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+ <property name="locations">
+ <list>
+ <value>org.wamblee.crawler.properties</value>
+ </list>
+ </property>
+ </bean>
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="program-info">
+ <xsl:apply-templates select="//xhtml:a"/>
+ <xsl:element name="title">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="//xhtml:tr[1]/xhtml:td[1]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:element name="keywords">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="//xhtml:tr[3]/xhtml:td[3]"/>
+ <xsl:with-param name="from" select="$newline"/>
+ <xsl:with-param name="to" select="''"/>
+ </xsl:call-template>
+ </xsl:element>
+ <xsl:element name="description">
+ <xsl:apply-templates select="//xhtml:tr[7]/xhtml:td[1]"/>
+ </xsl:element>
+ </xsl:element>
+ </xsl:template>
+
+
+
+ <xsl:template match="xhtml:a[ text() = 'Record' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:text>record</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>recorded</xsl:text>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:output method="xml"/>
+ <xsl:strip-space elements="xhtml:a"/>
+
+ <xsl:include href="utilities.xsl"/>
+
+ <!-- =====================================================
+ Copying template.
+ ===================================================== -->
+ <xsl:template match="@*|node()" mode="copy">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" mode="copy"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:element name="program-info">
+ <xsl:apply-templates select="//xhtml:a"/>
+ <xsl:element name="title">
+ <xsl:value-of select="//xhtml:h2[1]"/>
+ </xsl:element>
+ <xsl:element name="keywords">
+ <xsl:apply-templates select="//text()[count(preceding::xhtml:br)= 1]"/>
+ </xsl:element>
+ <xsl:element name="description">
+ <xsl:apply-templates select="//text()[count(preceding::xhtml:h2) = 2 and
+ count(following::xhtml:br) >= 4 and count(preceding::xhtml:br) >= 3]"/>
+ </xsl:element>
+ </xsl:element>
+ </xsl:template>
+
+
+
+ <xsl:template match="xhtml:a[ text() = 'Record' ]">
+ <xsl:element name="action">
+ <xsl:attribute name="name">
+ <xsl:text>record</xsl:text>
+ </xsl:attribute>
+ <xsl:attribute name="reference">
+ <xsl:value-of select="@href"/>
+ </xsl:attribute>
+ <xsl:attribute name="type">
+ <xsl:text>recorded</xsl:text>
+ </xsl:attribute>
+ <xsl:text>
+
+ </xsl:text>
+ </xsl:element>
+ </xsl:template>
+
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ version="1.0">
+
+ <xsl:template match="/">
+ <xsl:element name="result">
+ <xsl:choose>
+ <xsl:when test="count(//xhtml:h1[contains(text(), 'Error')]) = 1">
+ <xsl:choose>
+ <xsl:when test="count(//xhtml:body/text()[contains(., 'already in the')]) =
+ 1">
+ <xsl:text>DUPLICATE</xsl:text>
+ </xsl:when>
+ <xsl:when test="count(//xhtml:body/text()[contains(., 'conflicts with a
+ recording')]) =
+ 1">
+ <xsl:text>CONFLICT</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>ERROR</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:text>OK</xsl:text>
+ </xsl:otherwise>
+ </xsl:choose>
+
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="xhtml:body">
+ <xsl:value-of select="text()"/>
+ </xsl:template>
+
+ <xsl:template match="text()">
+ TEXT NODE
+ <xsl:value-of select="."/>
+ </xsl:template>
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="messages"/>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+
+ <xsl:template match="messages">
+ <h2>Messages</h2>
+ <ul>
+ <xsl:for-each select="message">
+ <li><font size="-1">
+ <xsl:value-of select="."/>
+ </font>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:include href="utilities.xsl"/>
+ <xsl:template match="report">
+ <xsl:text>KiSS crawler report</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <xsl:text>Possibly interesting programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+
+ <xsl:apply-templates select="messages"/>
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <xsl:apply-templates select="program"/>
+ </xsl:template>
+ <xsl:template match="recorded">
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for technical reasons.</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <xsl:text>*</xsl:text>
+ <xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>
+ <xsl:text>: </xsl:text>
+ <xsl:value-of select="name"/>
+ <xsl:text>(</xsl:text>
+ <xsl:value-of select="channel"/>
+ <xsl:text>/</xsl:text><xsl:value-of select="keywords"/>
+ <xsl:text>)</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:variable name="indent">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+ <xsl:call-template name="indent">
+ <xsl:with-param name="src">
+ <xsl:call-template name="word-wrap">
+ <xsl:with-param name="src"><xsl:value-of select="description"/></xsl:with-param>
+ <xsl:with-param name="width"><xsl:value-of select="72"/></xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="indentString">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+
+ </xsl:call-template>
+
+ <!--
+ <xsl:value-of select="$indent"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src"><xsl:value-of select="description"/></xsl:with-param>
+ <xsl:with-param name="from"><xsl:value-of select="$newline"/></xsl:with-param>
+ <xsl:with-param name="to"><xsl:value-of select="$newline"/><xsl:value-of select="$indent"/></xsl:with-param>
+ </xsl:call-template>
+ -->
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <xsl:text>Category: </xsl:text><xsl:value-of select="@name"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+
+ <xsl:template match="messages">
+ <xsl:text>Messages</xsl:text>
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$newline"/>
+ <xsl:for-each select="message">
+ <xsl:text>* </xsl:text>
+ <xsl:value-of select="."/>
+ <xsl:value-of select="$newline"/>
+ </xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-kissweb</artifactId>
+ <packaging>war</packaging>
+ <name>wamblee.org KiSS crawler web interface </name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-kiss</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>quartz</groupId>
+ <artifactId>quartz</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jstl</groupId>
+ <artifactId>jstl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>taglibs</groupId>
+ <artifactId>standard</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <webXml>${basedir}/src/webapp/WEB-INF/web.xml</webXml>
+ <warName>wamblee-crawler-kissweb</warName>
+ <warSourceDirectory>src/webapp</warSourceDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.util.Date;
+
+import org.wamblee.crawler.kiss.main.Report;
+
+/**
+ * Encapsulates the actual execution of the crawler.
+ * This interface makes it possible to test the scheduling logic
+ * in isolation.
+ *
+ */
+public interface CrawlerExecutor {
+
+ /**
+ * Executes the crawler.
+ * @param aDate Date the crawler is being triggered.
+ * @param The report from the crawler.
+ * @throws Exception
+ */
+ void execute(Date aDate, Report aReport) throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.util.Date;
+
+import org.wamblee.crawler.kiss.main.KissCrawler;
+import org.wamblee.crawler.kiss.main.Report;
+import org.wamblee.crawler.kiss.notification.Notifier;
+
+/**
+ * Implementation which executes the KiSS crawler for retrieving web content.
+ */
+public class CrawlerExecutorImpl implements CrawlerExecutor {
+
+ private String _crawlerConfig;
+ private String _programConfig;
+ private Notifier _notifier;
+
+ /**
+ * Constructs the crawler executor.
+ * @param aCrawlerConfig Crawler configuration file.
+ * @param aProgramConfig Program configuration file.
+ * @param aNotifier Object used to send notifications.
+ */
+ public CrawlerExecutorImpl(String aCrawlerConfig, String aProgramConfig, Notifier aNotifier) {
+ _crawlerConfig = aCrawlerConfig;
+ _programConfig = aProgramConfig;
+ _notifier = aNotifier;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler.CrawlerExecutor#execute(java.util.Date)
+ */
+ public void execute(Date aDate, Report aReport) throws Exception {
+ new KissCrawler(_crawlerConfig, _programConfig, _notifier, aReport);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+/**
+ * Interface to the scheduler specific for working with the crawler.
+ */
+public interface CrawlerScheduler {
+
+ /**
+ * Initializes the scheduler.
+ * @throws Exception In case of problems.
+ */
+ void initialize() throws Exception;
+
+ /**
+ * Checks if the crawler is running.
+ * @return True iff the crawler is running.
+ * @throws Exception In case of problems.
+ */
+ boolean isCrawlerRunning() throws Exception;
+
+ /**
+ * Schedules the crawler for immediate execution.
+ * @throws Exception In case of problems.
+ */
+ void scheduleNow() throws Exception;
+
+ /**
+ * Shuts down the scheduler.
+ * @throws Exception In case of problems.
+ */
+ void shutdown() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.crawler.kiss.main.Report;
+
+/**
+ * This class encapsulates the logic for deciding whether to
+ * run the crawler. This provides the mechanism to keep the
+ * scheduler simple (e.g. scheduling every hour) and providing
+ * more complex logic for determining whether to run the
+ * crawler.
+ */
+public class CrawlerStatus implements Serializable {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerStatus.class);
+
+ private CrawlerExecutor _crawler;
+ private Date _lastExecuted;
+ private boolean _lastResult;
+ private Exception _lastException;
+ private Report _lastReport;
+ private int _hourMin;
+ private int _hourMax;
+ private boolean _mustExecute;
+
+ /**
+ * Constructs the scheduler.
+ * The crawler will run if it is triggered in the range between the minimum (included)
+ * and maximum (included) hour of the day if either
+ * <ul>
+ * <li>it is triggered for the first time on the current day.</li>
+ * <li>an earlier crawling attempt on the same day failed. </li>
+ * </ul>
+ * @param aCrawler The interface through which the crawler is executed.
+ * @param aHourMin The crawler may only run if hour >= <code>aHourMin</code>
+ * @param aHourMax The crawler may only run if hour <= <code>aHourMax</code>
+ */
+ public CrawlerStatus(CrawlerExecutor aCrawler, int aHourMin, int aHourMax) {
+ _crawler = aCrawler;
+ _lastExecuted = new Date();
+ _lastResult = true; // the crawler will automatically run the next day.
+ _lastException = null;
+ _lastReport = null;
+ _hourMin = aHourMin;
+ _hourMax = aHourMax;
+ _mustExecute = false;
+ }
+
+ /**
+ * Determines whether or not the crawler must be run the next time it is triggered.
+ * @param aMustExecute If true then the crawler will run the next time it is triggered
+ * by the scheduler.
+ */
+ public void setMustExecute(boolean aMustExecute) {
+ _mustExecute = aMustExecute;
+ }
+
+ /**
+ * Called by a scheduled job. This determines whether the crawler must be run or
+ * not. This encapsulates the rukes for retrying and scheduling the crawler.
+ * @param aDate Time at which we are executing now.
+ */
+ public void execute(Date aDate) {
+
+ if (mustExecute(aDate)) {
+ LOG.info("Executing crawler at " + aDate);
+ Report report = new Report();
+ try {
+ _crawler.execute(aDate, report);
+ _lastResult = true;
+ _lastException = null;
+ } catch (Exception e) {
+ _lastResult = false;
+ _lastException = e;
+ } finally {
+ _lastExecuted = aDate;
+ _lastReport = report;
+ }
+ }
+ }
+
+ /**
+ * Gets the time the crawler was last executed.
+ * @return Time of last execution.
+ */
+ public Date getLastExecuted() {
+ return _lastExecuted;
+ }
+
+ /**
+ * Gets the result of the last execution.
+ * @return True iff last execution was a success.
+ */
+ public boolean getLastResult() {
+ return _lastResult;
+ }
+
+ /**
+ * Gets the exception thrown by the last execution.
+ * @return null if the last execution was successful or an exception
+ * otherwise.
+ */
+ public Exception getLastException() {
+ return _lastException;
+ }
+
+ /**
+ * Gets the last report from the scheduler.
+ * @return Report.
+ */
+ public Report getLastReport() {
+ return _lastReport;
+ }
+
+ /**
+ * Determines whether or not the crawler must be run.
+ * @param aDate Current time.
+ * @return True iff the crawler must be run.
+ */
+ private boolean mustExecute(Date aDate) {
+ if (_mustExecute) {
+ _mustExecute = false;
+ return true;
+ }
+ if ( _lastExecuted == null ) {
+ return false; // crawler must be started manually at least once after deployment.
+ }
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(aDate);
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ if ( hour < _hourMin ) {
+ return false;
+ }
+ if (hour > _hourMax ) {
+ return false;
+ }
+
+ if ( !lastExecutionWasOnSameDay(aDate)) {
+ return true; // First execution of today.
+ }
+ // last execution was on the same day.
+ if ( !_lastResult ) {
+ return true; // last execution of today was unsuccessful, retry.
+ }
+ return false; // already run successfully today.
+ }
+
+ /**
+ * Determines if the last execution was on the same day.
+ * @param aDate Current time.
+ * @return True iff last execution was on the same day.
+ */
+ private boolean lastExecutionWasOnSameDay(Date aDate) {
+ if ( _lastExecuted == null ) {
+ return false;
+ }
+ int curDay = getDayOfYear(aDate);
+ int lastDay = getDayOfYear(_lastExecuted);
+ return curDay == lastDay; // check can be invalid only if scheduling interval is one year,
+ // which is ridiculous.
+ }
+
+ /**
+ * Gets the day of the year
+ * @param aDate Date to compute day for.
+ */
+ private int getDayOfYear(Date aDate) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(aDate);
+ return calendar.get(Calendar.DAY_OF_YEAR);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling.quartz;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.StatefulJob;
+import org.wamblee.crawler.kiss.scheduling.CrawlerStatus;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * Quartz job to execute the crawler.
+ */
+public class CrawlerJob implements StatefulJob {
+
+ private static final Log LOG = LogFactory.getLog(CrawlerJob.class);
+
+ /**
+ * Constructs the job.
+ *
+ */
+ public CrawlerJob() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
+ */
+ public void execute(JobExecutionContext aContext)
+ throws JobExecutionException {
+ LOG.info("Job triggered");
+ try {
+ CrawlerStatus schedule = BeanKernel.getBeanFactory().find(
+ CrawlerStatus.class);
+ schedule.execute(aContext.getFireTime());
+ } catch (Exception e) {
+ throw new JobExecutionException("Error executing crawler", e, false);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.scheduling.quartz;
+
+import java.util.Date;
+import java.util.List;
+
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerFactory;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
+import org.quartz.TriggerUtils;
+import org.quartz.impl.StdSchedulerFactory;
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+
+/**
+ * Interface to the Quartz scheduler.
+ */
+public class QuartzCrawlerScheduler implements CrawlerScheduler {
+
+ /**
+ *
+ */
+ private static final String TRIGGER_NAME = "interval";
+
+ /**
+ *
+ */
+ private static final String JOB_NAME = "kisscrawler";
+
+ private Scheduler _scheduler;
+
+ private int _intervalInSeconds;
+
+ /**
+ * Constructs the quartz interface.
+ * @param aIntervalInSeconds Scheduling interval in seconds.
+ * @throws SchedulerException
+ */
+ public QuartzCrawlerScheduler(int aIntervalInSeconds) throws SchedulerException {
+ SchedulerFactory schedulerFactory = new StdSchedulerFactory();
+ _scheduler = schedulerFactory.getScheduler();
+ _intervalInSeconds = aIntervalInSeconds;
+ }
+
+ /**
+ * Initializes the scheduler.
+ * @throws SchedulerException
+ */
+ public void initialize() throws SchedulerException {
+ _scheduler.start();
+
+ JobDetail jobDetail = new JobDetail(JOB_NAME, null, CrawlerJob.class);
+ Trigger trigger = TriggerUtils.makeSecondlyTrigger(_intervalInSeconds);
+ //trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));
+ trigger.setStartTime(new Date());
+ trigger.setName(TRIGGER_NAME);
+
+ _scheduler.scheduleJob(jobDetail, trigger);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler#isCrawlerRunning()
+ */
+ public boolean isCrawlerRunning() throws Exception {
+ List jobs = _scheduler.getCurrentlyExecutingJobs();
+ return jobs.size() > 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.crawler.kiss.scheduling.CrawlerScheduler#scheduleNow()
+ */
+ public void scheduleNow() throws Exception {
+ Trigger trigger = new SimpleTrigger("immediate", null);
+ trigger.setJobName(JOB_NAME);
+ _scheduler.scheduleJob(trigger);
+ }
+
+ /**
+ * Shuts down the scheduler.
+ * @throws SchedulerException
+ */
+ public void shutdown() throws SchedulerException {
+ _scheduler.shutdown();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.servlet;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * The mechanism for kick starting the scheduling of the KiSS crawler.
+ */
+public class Application implements ServletContextListener {
+
+ /**
+ * Constructs the listener.
+ *
+ */
+ public Application() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+ */
+ public void contextInitialized(ServletContextEvent aEvent) {
+ aEvent.getServletContext().log("KiSS Crawler initializing");
+ try {
+ getScheduler().initialize();
+ } catch (Exception e) {
+ aEvent.getServletContext().log("Error scheduling job", e);
+ return;
+ }
+ aEvent.getServletContext().log("KiSS Crawler initialized");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+ */
+ public void contextDestroyed(ServletContextEvent aEvent) {
+ aEvent.getServletContext().log("KiSS Crawler shutting down");
+ try {
+ getScheduler().shutdown();
+ } catch (Exception e) {
+ aEvent.getServletContext().log("Error scheduling job", e);
+ return;
+ }
+ aEvent.getServletContext().log("KiSS Crawler shut down complete");
+ }
+
+ /**
+ * Gets the scheduler from Spring.
+ * @return Scheduler.
+ */
+ private CrawlerScheduler getScheduler() {
+ return BeanKernel.getBeanFactory().find(CrawlerScheduler.class);
+ }
+
+ public static void main(String[] aArgs) throws Exception {
+ Application application = new Application();
+ application.getScheduler().initialize();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.servlet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.wamblee.crawler.kiss.main.Report;
+import org.wamblee.crawler.kiss.notification.Notifier;
+import org.wamblee.crawler.kiss.scheduling.CrawlerScheduler;
+import org.wamblee.crawler.kiss.scheduling.CrawlerStatus;
+import org.wamblee.general.BeanKernel;
+
+/**
+ *
+ */
+public class CrawlerServlet extends HttpServlet {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doPost(HttpServletRequest aRequest,
+ HttpServletResponse aResponse) throws ServletException, IOException {
+
+ CrawlerScheduler scheduler = BeanKernel.getBeanFactory().find(
+ CrawlerScheduler.class);
+ CrawlerStatus status = BeanKernel.getBeanFactory().find(
+ CrawlerStatus.class);
+
+ try {
+ if (aRequest.getParameter("details") != null) {
+ Report report = status.getLastReport();
+ if (report != null) {
+ Notifier notifier = BeanKernel.getBeanFactory().find(Notifier.class);
+ aResponse.setContentType("text/html");
+ OutputStream os = aResponse.getOutputStream();
+ os.write(notifier.asHtml(report.asXml()).getBytes());
+ return;
+ }
+ }
+ if (aRequest.getParameter("runnow") != null) {
+ status.setMustExecute(true);
+ scheduler.scheduleNow();
+ aResponse.sendRedirect("");
+ return;
+ }
+ aRequest.setAttribute("running", scheduler.isCrawlerRunning());
+ aRequest.setAttribute("lastExecuted", status.getLastExecuted());
+ aRequest.setAttribute("lastResult", status.getLastResult());
+ aRequest.setAttribute("lastException", status.getLastException());
+ aRequest.setAttribute("lastReport", status.getLastReport());
+ String msg = "";
+ Throwable e = status.getLastException();
+ while (e != null) {
+ msg = msg + e.getClass().getName() + ": " + e.getMessage()
+ + "<br/>";
+ e = e.getCause();
+ }
+ aRequest.setAttribute("lastMessage", msg);
+ } catch (Exception e) {
+ throw new ServletException("Error getting status", e);
+ }
+ aRequest.getRequestDispatcher("WEB-INF/overview.jsp").forward(aRequest,
+ aResponse);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doGet(HttpServletRequest aRequest,
+ HttpServletResponse aResponse) throws ServletException, IOException {
+ doPost(aRequest, aResponse);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.crawler.kiss.spring;
+import org.wamblee.general.SpringBeanFactory;
+
+
+/**
+ * Bean factory for the crawler application.
+ */
+public class CrawlerBeanFactory extends SpringBeanFactory {
+ private static final String SELECTOR_NAME = "beanRefContext.xml";
+ private static final String FACTORY_NAME = "crawler";
+
+ /**
+ * Constructs the bean factory.
+ *
+ */
+ public CrawlerBeanFactory() {
+ super(SELECTOR_NAME, FACTORY_NAME);
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="crawler"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.crawler.properties.xml</value>
+ <value>org.wamblee.crawler.notification.xml</value>
+ <value>org.wamblee.crawler.kiss.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+##############################################################################
+# Class name of the beanfactory used by the crawler application
+##############################################################################
+
+org.wamblee.beanfactory.class=org.wamblee.crawler.kiss.spring.CrawlerBeanFactory
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <!-- The object that tells quartz how to schedule the crawler -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerScheduler"
+ class="org.wamblee.crawler.kiss.scheduling.quartz.QuartzCrawlerScheduler">
+ <constructor-arg><value type="int">3600</value></constructor-arg>
+ </bean>
+
+ <!-- The object which executes the crawler -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerExecutor"
+ class="org.wamblee.crawler.kiss.scheduling.CrawlerExecutorImpl">
+ <constructor-arg><value>${org.wamblee.crawler.config.epg}</value></constructor-arg>
+ <constructor-arg><value>${org.wamblee.crawler.config.programs}</value></constructor-arg>
+ <constructor-arg><ref bean="org.wamblee.crawler.kiss.notification.Notifier"/></constructor-arg>
+ </bean>
+
+ <!-- The object that determines whether to execute the crawler when it is signalled by
+ the scheduler. -->
+ <bean id="org.wamblee.crawler.kiss.scheduling.CrawlerStatus"
+ class="org.wamblee.crawler.kiss.scheduling.CrawlerStatus">
+ <constructor-arg><ref local="org.wamblee.crawler.kiss.scheduling.CrawlerExecutor"/></constructor-arg>
+ <!-- The interval of the day in hours [hourmin, hourmax] over which crawling will be done and
+ retried if necessary -->
+ <constructor-arg><value type="int">19</value></constructor-arg>
+ <constructor-arg><value type="int">24</value></constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+
+
+############################################################################
+# Mail server configuration
+############################################################################
+org.wamblee.crawler.smtp.host=shikra
+org.wamblee.crawler.smtp.port=25
+org.wamblee.crawler.smtp.username=
+org.wamblee.crawler.smtp.password=
+
+############################################################################
+# Mail notification configuration
+############################################################################
+org.wamblee.crawler.notification.from=kiss@wamblee.org
+org.wamblee.crawler.notification.to=erik@brakkee.org
+org.wamblee.crawler.notification.subject=Recording summary for tomorrow
+
+############################################################################
+# Configuration of the crawler
+############################################################################
+org.wamblee.crawler.config.epg=/home/erik/crawler/config.xml
+org.wamblee.crawler.config.programs=/home/erik/crawler/programs.xml
+
--- /dev/null
+Manifest-Version: 1.0\r
+Class-Path: \r
+\r
--- /dev/null
+
+<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>KiSS Crawler overview page</title>
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="expires" content="0">
+ <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
+ <meta http-equiv="description" content="This is my page">
+
+ <!--
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ -->
+ </head>
+
+ <body>
+ <h1>KiSS Crawler Overview</h1>
+
+ <TABLE border="1">
+ <tr>
+ <td>
+ Currently running:
+ </td>
+ <td>
+ <c:out value="${running}"/>
+ </td>
+ </tr>
+ <c:if test="${lastReport != null}">
+ <tr>
+ <td>
+ Last executed at:
+ </td>
+ <td>
+ <c:out value="${lastExecuted}"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last result:
+ </td>
+ <td>
+ <c:out value="${lastResult}"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last message:
+ </td>
+ <td>
+ <c:out value="${lastMessage}" escapeXml="false"/>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ Last report:
+ </td>
+ <td>
+ <a href="?details=1">details</a>
+ </td>
+ </tr>
+ </c:if>
+ </TABLE>
+ <c:if test="${!running}">
+ <FORM action="runnow">
+ <INPUT type="submit" name="runnow" value="Run Crawler as soon as possible">
+ </FORM>
+ </c:if>
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.4"
+ xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+ http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+ <listener>
+ <listener-class>org.wamblee.crawler.kiss.servlet.Application</listener-class>
+ </listener>
+
+ <servlet>
+ <servlet-name>CrawlerServlet</servlet-name>
+ <servlet-class>org.wamblee.crawler.kiss.servlet.CrawlerServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>CrawlerServlet</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+</web-app>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler</artifactId>
+ <packaging>pom</packaging>
+ <version>0.2-SNAPSHOT</version>
+ <name>wamblee.org KiSS crawler all</name>
+ <url>http://wamblee.org</url>
+ <modules>
+ <module>basic</module>
+ <module>kiss</module>
+ <module>kissweb</module>
+ </modules>
+
+ <dependencies>
+ </dependencies>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-basic</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler-kiss</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <descriptors>
+ <descriptor>src/assembly/kiss-application.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>binpackage</id>
+ <phase>package</phase>
+ <goals>
+ <!-- goal>single</goal -->
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+<assembly>
+ <id>kissbin</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <moduleSets>
+ <moduleSet>
+ <includes>
+ <include>org.wamblee:wamblee-crawler-kiss</include>
+ </includes>
+ <binaries>
+ <includeDependencies>true</includeDependencies>
+ <outputDirectory>lib</outputDirectory>
+ <unpack>false</unpack>
+ </binaries>
+ </moduleSet>
+
+ </moduleSets>
+
+ <fileSets>
+ <fileSet>
+ <directory>kiss/conf/kiss</directory>
+
+ <outputDirectory>conf</outputDirectory>
+ <includes>
+ <include>config.xml.example</include>
+ <include>programs.xml</include>
+ <include>org.wamblee.crawler.properties</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>kiss/conf/kiss</directory>
+
+ <outputDirectory>bin</outputDirectory>
+ <includes>
+ <include>run.*</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>kiss/target</directory>
+
+ <outputDirectory>lib</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+ <dependencySets>
+ <dependencySet>
+ <scope>runtime</scope>
+ </dependencySet>
+ </dependencySets>
+
+
+</assembly>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-crawler</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-gps</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org GPS utilities</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jfreechart</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jcommon</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+
+/**
+ * Represents a coordinate system.
+ */
+public interface CoordinateSystem extends Serializable {
+
+ /**
+ * Conversion to a reference coordinate system.
+ * @param aCoordinates Coordinates.
+ * @return Coordinates in the reference system.
+ */
+ Coordinates toReferenceSystem(Coordinates aCoordinates);
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+/**
+ * Coordinates in some 3-dimensional coordinate system.
+ */
+public class Coordinates implements Serializable {
+
+ private double _x1;
+ private double _x2;
+ private double _x3;
+
+ /**
+ * Constructs the coordinates.
+ * @param aX1 First coordinate.
+ * @param aX2 Second coordinate.
+ * @param aX3 Third coordinate.
+ */
+ public Coordinates(double aX1, double aX2, double aX3) {
+ _x1 = aX1;
+ _x2 = aX2;
+ _x3 = aX3;
+ }
+
+ public double getX1() {
+ return _x1;
+ }
+
+ public double getX2() {
+ return _x2;
+ }
+
+ public double getX3() {
+ return _x3;
+ }
+
+ public double getX(int i) {
+ switch (i) {
+ case 1: return _x1;
+ case 2: return _x2;
+ case 3: return _x3;
+ }
+ throw new IllegalArgumentException("coordinate out of range " + i);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "(" + getX1() + ", " + getX2() + ", " + getX3() + ")";
+ }
+
+ public Coordinates add(Coordinates aC) {
+ return new Coordinates(_x1 + aC._x1, _x2 + aC._x2, _x3 + aC._x3);
+ }
+
+ public Coordinates subtract(Coordinates aC) {
+ return new Coordinates(_x1 - aC._x1, _x2 - aC._x2, _x3 - aC._x3);
+ }
+
+ public double innerProduct(Coordinates aC) {
+ return _x1 * aC._x1 + _x2 * aC._x2 + _x3 * aC._x3;
+ }
+
+ public Coordinates outerProduct(Coordinates aC) {
+ return new Coordinates(
+ _x2*aC._x3 - _x3*aC._x2,
+ -_x1*aC._x3 + _x3*aC._x1,
+ _x1*aC._x2 - _x2*aC._x1
+ );
+ }
+
+ public double norm() {
+ return Math.sqrt(innerProduct(this));
+ }
+
+ public Coordinates scale(double aMultiplier) {
+ return new Coordinates(_x1*aMultiplier, _x2*aMultiplier, _x3*aMultiplier);
+ }
+
+ public Coordinates normalize() {
+ return scale(1.0/norm());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import org.wamblee.general.Pair;
+
+/**
+ * Represents a plane. Usually used to represent a tangent plane to the earth surface to
+ * locally approximate the earth as flat.
+ */
+public class Plane {
+
+ private static final double EPS = 1e-4;
+
+ private Coordinates _point;
+ private Coordinates _normal;
+ private Coordinates _north;
+ private Coordinates _east;
+
+ /**
+ * Constructs a plane.
+ * @param aPoint Point on the plane.
+ * @param aNormal Normal, not necessarily normalized.
+ */
+ public Plane(Point aPoint, Point aNormal) {
+ _point = aPoint.getReferenceCoordinates();
+ _normal = aNormal.getReferenceCoordinates().normalize();
+ Coordinates north = new Coordinates(0.0, 0.0, 1.0);
+ _north = north.subtract(_normal.scale(north.innerProduct(_normal))).normalize();
+ _east = _north.outerProduct(_normal);
+
+ if ( _normal.innerProduct(_north) > EPS ) {
+ throw new IllegalArgumentException("North access is not within the plane");
+ }
+ }
+
+ /**
+ * Projects a point onto the plane.
+ * @param aPoint Point to project.
+ * @return Projected point.
+ */
+ private Coordinates project(Point aPoint) {
+ Coordinates ref = aPoint.getReferenceCoordinates();
+ double lambda = _normal.innerProduct(
+ _point.subtract(ref));
+ return ref.add(_normal.scale(lambda));
+ }
+
+ /**
+ * Returns normalized coordinates within the plane of the projection of a point.
+ */
+ public Pair<Double,Double> normalizedProjection(Point aPoint) {
+ Coordinates projection = project(aPoint);
+ Coordinates delta = projection.subtract(_point);
+ double x1 = delta.innerProduct(_north);
+ double x2 = delta.innerProduct(_east);
+ return new Pair<Double,Double>(x1, x2);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import java.io.Serializable;
+
+
+/**
+ * Represents a point in some coordinate system.
+ */
+public class Point implements Serializable {
+
+ private Coordinates _coordinates;
+ private CoordinateSystem _system;
+
+ /**
+ * Constructs the point.
+ * @param aCoordinates Coordinates of the point in its coordinate system.
+ * @param aSystem Coordinate system.
+ */
+ public Point(Coordinates aCoordinates, CoordinateSystem aSystem) {
+ _coordinates = aCoordinates;
+ _system = aSystem;
+ }
+
+ /**
+ * Gets the coordinates in the point's coordinate system.
+ * @return Coordinates.
+ */
+ public Coordinates getCoordinates() {
+ return _coordinates;
+ }
+
+ public Coordinates getReferenceCoordinates() {
+ return _system.toReferenceSystem(_coordinates);
+ }
+
+ /**
+ * Gets the coordinate system.
+ * @return Coordinate system.
+ */
+ public CoordinateSystem getCoordinateSystem() {
+ return _system;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getCoordinates().toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+
+/**
+ * Reference coordinate system which is the basis for defining metrics.
+ * This is a Cartesian coordinate system.
+ */
+public class ReferenceCoordinateSystem implements CoordinateSystem {
+
+ /**
+ * Constructs the coordinate system.
+ *
+ */
+ public ReferenceCoordinateSystem() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ return aCoordinates;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.gpx.CoordinateSystem#distance(org.wamblee.gpx.Coordinates,
+ * org.wamblee.gpx.Coordinates)
+ */
+ private static double distance(Coordinates aC1, Coordinates aC2) {
+ return Math.sqrt(square(aC1.getX1() - aC2.getX1())
+ + square(aC1.getX2() - aC2.getX2())
+ + square(aC1.getX3() - aC2.getX3()));
+ }
+
+ private static double square(double x) {
+ return x * x;
+ }
+
+ /**
+ * Computes the distance between two points in arbitrary coordinate systems.
+ * @param aP1 First point.
+ * @param aP2 Second point.
+ * @return Distance.
+ */
+ public static double distance(Point aP1, Point aP2) {
+ return distance( aP1.getCoordinateSystem().toReferenceSystem(aP1.getCoordinates()),
+ aP2.getCoordinateSystem().toReferenceSystem(aP2.getCoordinates()));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import static java.lang.Math.PI;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+
+
+/**
+ * Represents the coordinate system for a GPS measurement identified by
+ * <ul>
+ * <li> x1: latitude in degrees </li>
+ * <li> x2: longitude in degrees </li>
+ * <li> x3: elevation in meters </li>
+ * </ul>
+ * This coordinate system models the earth as a sphere of a specific radius.
+ */
+public class SphericalCoordinateSystem implements CoordinateSystem {
+ /**
+ * Earth radius in meters.
+ */
+ private static final double EARTH_RADIUS = 6371000;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ double latrad = radians(aCoordinates.getX1());
+ double lonrad = radians(aCoordinates.getX2());
+ double coslat = cos(latrad);
+ double sinlat = sin(latrad);
+ double coslon = cos(lonrad);
+ double sinlon = sin(lonrad);
+
+ double trueElevation = EARTH_RADIUS + aCoordinates.getX3();
+ return new Coordinates(trueElevation*coslat*coslon,
+ trueElevation*coslat*sinlon,
+ trueElevation*sinlat);
+
+ }
+
+ private double radians(double aDegrees) {
+ return aDegrees/180.0*PI;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.geometry;
+
+import static java.lang.Math.PI;
+import static java.lang.Math.cos;
+import static java.lang.Math.sin;
+
+
+/**
+ * Represents the WGS 84 coordinate system for a GPS measurement identified by
+ * <ul>
+ * <li> x1: latitude in degrees </li>
+ * <li> x2: longitude in degrees </li>
+ * <li> x3: elevation in meters </li>
+ * </ul>
+ * WGS84 models the earth as an ellipse.
+ */
+public class Wgs84CoordinateSystem implements CoordinateSystem {
+ /*
+ * Ellipsoide parameters, where the ellipsoide is defined by
+ *
+ * (x^2 + y^2)/a^2 + z^2/b^2 = 1
+ */
+
+ /**
+ * The radius of the ellipse at the equator
+ */
+ private static final double A = 6378137.000;
+
+ /**
+ * The distance of the North and South poles to the center of the ellipsoide.
+ */
+ private static final double B = 6356752.314;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.gpx.CoordinateSystem#toReferenceSystem(org.wamblee.gpx.Coordinates)
+ */
+ public Coordinates toReferenceSystem(Coordinates aCoordinates) {
+ double latrad = radians(aCoordinates.getX1());
+ double lonrad = radians(aCoordinates.getX2());
+ double coslat = cos(latrad);
+ double sinlat = sin(latrad);
+ double coslon = cos(lonrad);
+ double sinlon = sin(lonrad);
+
+ double r = A*B/Math.sqrt(B*B*coslat*coslat + A*A*sinlat*sinlat) + aCoordinates.getX3();
+
+ return new Coordinates(r*coslat*coslon,
+ r*coslat*sinlon,
+ r*sinlat);
+
+ }
+
+ private double radians(double aDegrees) {
+ return aDegrees/180.0*PI;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.track;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.gps.geometry.Point;
+
+/**
+ * Represents a GPS track.
+ */
+public class Track implements Serializable {
+
+ private List<TrackPoint> _points;
+
+ /**
+ * Constructs an empty track.
+ *
+ */
+ public Track() {
+ _points = new ArrayList<TrackPoint>();
+ }
+
+ /**
+ * Adds a point to a track.
+ * @param aPoint Point.
+ */
+ public void addPoint(TrackPoint aPoint) {
+ _points.add(aPoint);
+ }
+
+ /**
+ * @return Number of points in the track.
+ */
+ public int size() {
+ return _points.size();
+ }
+
+ public double getMinCoordinate(int i) {
+ if ( size() == 0 ) {
+ throw new IllegalArgumentException("empty track");
+ }
+ double min = getPoint(0).getCoordinates().getX(i);
+ for (int j = 1; j < size(); j++) {
+ min = Math.min(min, getPoint(j).getCoordinates().getX(i));
+ }
+ return min;
+ }
+
+ public double getMaxCoordinate(int i) {
+ if ( size() == 0 ) {
+ throw new IllegalArgumentException("empty track");
+ }
+ double max = getPoint(0).getCoordinates().getX(i);
+ for (int j = 1; j < size(); j++) {
+ max = Math.max(max, getPoint(j).getCoordinates().getX(i));
+ }
+ return max;
+ }
+
+ /**
+ * Gets the point at the given inded.
+ * @param aIndex 0 <= aIndex < size()
+ * @return Point.
+ */
+ public Point getPoint(int aIndex) {
+ return _points.get(aIndex);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gps.track;
+
+import org.wamblee.gps.geometry.Coordinates;
+import org.wamblee.gps.geometry.Point;
+import org.wamblee.gps.geometry.Wgs84CoordinateSystem;
+
+
+/**
+ * A point from a GPS track.
+ *
+ * TODO should be extended with additional information (e.g. date/time if available).
+ */
+public class TrackPoint extends Point {
+
+ /**
+ * Constructs the point.
+ * @param aLatitude Latitude in degrees.
+ * @param aLongitude Longitude in degrees.
+ * @param aElevation Elevation in metres.
+ */
+ public TrackPoint(double aLatitude, double aLongitude, double aElevation) {
+ super(new Coordinates(aLatitude, aLongitude, aElevation), new Wgs84CoordinateSystem());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.wamblee.gps.track.Track;
+import org.wamblee.gps.track.TrackPoint;
+import org.wamblee.xml.DomUtils;
+import org.wamblee.xml.XMLException;
+
+/**
+ * Parser for GPX tracks.
+ */
+public class GpxParser {
+
+ private static final String SCHEMA_RESOURCE = "gpx.xsd";
+
+ public GpxParser() {
+ // Empty.
+ }
+
+ public Track parse(InputStream aIs) throws XMLException {
+ Document doc = DomUtils.convert(DomUtils.read(aIs));
+ return parse(doc);
+ }
+
+ /**
+ * @param doc
+ */
+ public Track parse(Document doc) {
+ Track track = new Track();
+ Element root = doc.getRootElement().element("trk").element("trkseg");
+ for ( Iterator i =root.elementIterator("trkpt"); i.hasNext(); ) {
+ Element trkpt = (Element)i.next();
+ track.addPoint(parseTrackPoint(trkpt));
+ }
+ return track;
+ }
+
+ /**
+ * @param trkpt
+ */
+ private TrackPoint parseTrackPoint(Element trkpt) {
+ //System.out.println(trkpt.asXML() + "|\n");
+ double latitude = new Double(trkpt.attributeValue("lat"));
+ double longitude = new Double(trkpt.attributeValue("lon"));
+ Element ele = trkpt.element("ele");
+ double elevation = 0.0;
+ if ( ele != null ) {
+ elevation = new Double(ele.getText());
+ }
+ //System.out.println(" lat = " + lat + " lon = " + lon + " ele = " + ele);
+ return new TrackPoint(latitude, longitude, elevation);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.awt.Color;
+import java.awt.Image;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartFrame;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.wamblee.general.Pair;
+import org.wamblee.gps.geometry.Plane;
+import org.wamblee.gps.geometry.Point;
+import org.wamblee.gps.geometry.ReferenceCoordinateSystem;
+import org.wamblee.gps.track.Track;
+import org.wamblee.utils.JpegUtils;
+
+/**
+ * Parses a GPX file and prints out a data file with each trackpoints distance from the start of the
+ * track and its elevation, separated 0by a space.
+ */
+public class GpxPlotter {
+
+ public static void main(String[] aArgs) throws Exception {
+ File file = new File(aArgs[0]);
+ GpxParser parser = new GpxParser();
+ Track track = parser.parse(new FileInputStream(file));
+
+ List<Pair<Double,Double>> elevationProfile = computeElevationProfile(track);
+ printTrack(elevationProfile);
+ computeTotalClimb(elevationProfile);
+ plotElevationProfile(elevationProfile);
+ List<Pair<Double,Double>> trackXy = computeTrackXY(track);
+ List<Pair<Double,Double>> trackLatLon = computeTrackLatLon(track);
+ plotTrack(trackLatLon);
+ }
+
+ private static List<Pair<Double, Double>> computeElevationProfile(Track aTrack) {
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ double distance = 0.0;
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ results.add(new Pair<Double,Double>(distance, point.getCoordinates().getX3()));
+ if ( i+1 < aTrack.size()) {
+ Point nextPoint = aTrack.getPoint(i+1);
+ distance += ReferenceCoordinateSystem.distance(point, nextPoint);
+ }
+ }
+ return results;
+ }
+
+ private static List<Pair<Double, Double>> computeTrackXY(Track aTrack) {
+ Point reference = aTrack.getPoint(0);
+ Plane plane = new Plane(reference, reference); // assume the earth is spherical.
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ Pair<Double,Double> projection = plane.normalizedProjection(point);
+ results.add(projection);
+ System.out.println(point);
+ }
+ return results;
+ }
+
+ private static List<Pair<Double, Double>> computeTrackLatLon(Track aTrack) {
+ List<Pair<Double,Double>> results = new ArrayList<Pair<Double,Double>>();
+ for (int i = 0; i < aTrack.size(); i++) {
+ Point point = aTrack.getPoint(i);
+ results.add(new Pair<Double,Double>(point.getCoordinates().getX1(), point.getCoordinates().getX2()));
+ }
+ return results;
+ }
+
+
+
+ private static void printTrack(List<Pair<Double,Double>> aHeightProfile) {
+ for (Pair<Double,Double> point: aHeightProfile) {
+ System.out.println(point.getFirst() + " " + point.getSecond());
+ }
+ }
+
+ private static void computeTotalClimb(List<Pair<Double,Double>> aHeightProfile) {
+ double result = 0.0;
+
+ double lastHeight = aHeightProfile.get(0).getSecond();
+ for ( int i = 1; i < aHeightProfile.size(); i++) {
+ double height = aHeightProfile.get(i).getSecond();
+ if ( height > lastHeight) {
+ result += (height-lastHeight);
+ }
+ lastHeight = height;
+ }
+ System.out.println("Total climb: " + result);
+ }
+
+ private static void plotElevationProfile(List<Pair<Double,Double>> aHeightProfile) throws IOException {
+ XYSeriesCollection dataset = createDataset(aHeightProfile, "height");
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Height Profile",
+ "Distance(m)",
+ "Height(m)",
+ dataset,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false);
+ ChartUtilities.writeChartAsPNG(new FileOutputStream("height.png"), chart, 600, 300);
+ ChartFrame frame = new ChartFrame("test", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ private static void plotTrack(List<Pair<Double,Double>> aPoints) throws IOException, InterruptedException {
+ XYSeriesCollection dataset = createDataset(aPoints, "track");
+ JFreeChart chart = createLineChart(dataset);
+
+ Pair<Pair<Double,Double>,Pair<Double,Double>> bounds = getBounds(aPoints);
+
+ chart.getXYPlot().getDomainAxis().setLowerBound(bounds.getFirst().getFirst());
+ chart.getXYPlot().getDomainAxis().setUpperBound(bounds.getFirst().getSecond());
+
+ chart.getXYPlot().getRangeAxis().setLowerBound(bounds.getSecond().getFirst());
+ chart.getXYPlot().getRangeAxis().setUpperBound(bounds.getSecond().getSecond());
+
+ Image background = JpegUtils.loadJpegImage(new FileInputStream("/home/erik/vakantie.jpg"));
+ chart.getPlot().setBackgroundImage(background);
+
+ XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer)chart.getXYPlot().getRenderer();
+ renderer.setShapesVisible(true);
+ renderer.setShapesFilled(true);
+ renderer.setPaint(Color.BLACK);
+
+ ChartUtilities.writeChartAsPNG(new FileOutputStream("test.png"), chart, 1280, 800);
+ ChartFrame frame = new ChartFrame("test", chart);
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ /**
+ * @param dataset
+ * @return
+ */
+ private static JFreeChart createLineChart(XYSeriesCollection dataset) {
+ NumberAxis xAxis = new NumberAxis("S->N");
+ xAxis.setAutoRangeIncludesZero(false);
+
+ NumberAxis yAxis = new NumberAxis("W->E");
+ yAxis.setAutoRangeIncludesZero(false);
+
+ XYItemRenderer renderer = new XYLineAndShapeRenderer(true, false);
+ XYPlot plot = new ZoomableBackgroundXYPlot(dataset, xAxis, yAxis, renderer);
+ plot.setOrientation(PlotOrientation.HORIZONTAL);
+
+ JFreeChart chart = new JFreeChart(
+ "Track", JFreeChart.DEFAULT_TITLE_FONT, plot, true
+ );
+
+ return chart;
+ /*
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ "Track",
+ "S->N",
+ "W->E",
+ dataset,
+ PlotOrientation.HORIZONTAL,
+ true,
+ true,
+ false);
+ return chart;
+ */
+ }
+
+ /**
+ * @param aHeightProfile
+ * @return
+ */
+ private static XYSeriesCollection createDataset(List<Pair<Double, Double>> aHeightProfile, String aName) {
+ XYSeries series = new XYSeries(aName, false);
+ for (Pair<Double,Double> point: aHeightProfile) {
+ series.add(point.getFirst(), point.getSecond());
+ }
+ XYSeriesCollection dataset = new XYSeriesCollection(series);
+ return dataset;
+ }
+
+ private static Pair<Pair<Double,Double>,Pair<Double,Double>> getBounds(List<Pair<Double,Double>> aList) {
+ Pair<Double,Double> first = aList.get(0);
+ double minx= first.getFirst();
+ double maxx = minx;
+ double miny = first.getSecond();
+ double maxy = miny;
+
+ for (int i = 0; i < aList.size(); i++) {
+ Pair<Double,Double> value = aList.get(i);
+ minx = Math.min(minx, value.getFirst());
+ maxx = Math.max(maxx, value.getFirst());
+ miny = Math.min(miny, value.getSecond());
+ maxy = Math.max(maxy, value.getSecond());
+ }
+ if ( maxx == minx ) {
+ maxx += 1.0; // to avoid problems.
+ }
+ if ( maxy == miny ) {
+ maxy += 1.0; // to avoid problems.
+ }
+ final double paddingFactor = 0.3; // allow some space around min and max
+ return new Pair<Pair<Double,Double>,Pair<Double,Double>>(
+ new Pair<Double,Double>( minx - paddingFactor*(maxx-minx),
+ maxx + paddingFactor*(maxx-minx)),
+ new Pair<Double,Double>( miny - paddingFactor*(maxy-miny),
+ maxy + paddingFactor*(maxy-miny))
+ );
+ }
+}
+
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.gpx;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.data.xy.XYDataset;
+
+/**
+ * Extension of XY plot that provides automatic zooming of the background
+ * image.
+ */
+public class ZoomableBackgroundXYPlot extends XYPlot {
+
+ /*
+ * Initial domain axis.
+ */
+ private double _x1 = 1.0;
+ private double _x2 = -1.0; // _x2 < _x1 initially to make signify uninitialized values.
+
+ /*
+ * Initial range axis.
+ */
+ private double _y1 = 1.0;
+ private double _y2 = -1.0; // _y2 < _y1 initially to make signify uninitialized values.
+
+ public ZoomableBackgroundXYPlot(XYDataset aDataset,
+ ValueAxis aDomainAxis, ValueAxis aRangeAxis, XYItemRenderer aRenderer) {
+ super(aDataset, aDomainAxis, aRangeAxis, aRenderer);
+ }
+
+ /* (non-Javadoc)
+ * @see org.jfree.chart.plot.Plot#drawBackgroundImage(java.awt.Graphics2D, java.awt.geom.Rectangle2D)
+ */
+ @Override
+ protected void drawBackgroundImage(Graphics2D g2, Rectangle2D area) {
+ //System.out.println("--------");
+ //System.out.println("Area: " + area);
+ //System.out.println("Graphics clip: " + g2.getClipBounds());
+ //System.out.println("Domain axis: " + getDomainAxis().getLowerBound() + " " +
+ // getDomainAxis().getUpperBound());
+
+ // Get the current domain axis bounds
+ double y1 = getDomainAxis().getLowerBound();
+ double y2 = getDomainAxis().getUpperBound();
+ double x1 = getRangeAxis().getLowerBound();
+ double x2 = getRangeAxis().getUpperBound();
+
+ if ( _x2 < _x1 ) {
+ // initial domain axis bounds
+ _y1 = y1;
+ _y2 = y2;
+ _x1 = x1;
+ _x2 = x2;
+ }
+
+ Image background = getBackgroundImage();
+ int width = background.getWidth(null);
+ int height = background.getHeight(null);
+
+ // Determine the part of the image to be drawn on the screen based on the scaling
+ // of the domain axes.
+ int imageX1 = (int)Math.round(1 + (x1 - _x1)*(width-1)/(_x2 - _x1));
+ int imageX2 = (int)Math.round(1 + (x2 - _x1)*(width-1)/(_x2 - _x1));
+ // Note: y-axis of image goes from bottom to top.
+ int imageY2 = (int)Math.round(height + (y1 - _y1)*(1-height)/(_y2 - _y1));
+ int imageY1 = (int)Math.round(height + (y2 - _y1)*(1-height)/(_y2 - _y1));
+
+ // Draw the correct part of the image on the screen.
+ g2.drawImage(background, (int)area.getMinX(), (int)area.getMinY(), (int)area.getMaxX(), (int)area.getMaxY(),
+ imageX1, imageY1, imageX2, imageY2, null);
+
+ // System.out.println("========");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<xsd:schema\r
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
+ xmlns="http://www.topografix.com/GPX/1/1"\r
+ targetNamespace="http://www.topografix.com/GPX/1/1"\r
+ elementFormDefault="qualified">\r
+\r
+<xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX schema version 1.1 - For more information on GPX and this schema, visit http://www.topografix.com/gpx.asp\r
+\r
+ GPX uses the following conventions: all coordinates are relative to the WGS84 datum. All measurements are in metric units.\r
+ </xsd:documentation>\r
+</xsd:annotation>\r
+\r
+ <xsd:element name="gpx" type="gpxType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX is the root element in the XML file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:complexType name="gpxType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPX documents contain a metadata header, followed by waypoints, routes, and tracks. You can add your own elements\r
+ to the extensions section of the GPX document.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="metadata" type="metadataType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Metadata about the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="wpt" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of waypoints.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="rte" type="rteType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of routes.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="trk" type="trkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of tracks.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+\r
+ <xsd:attribute name="version" type="xsd:string" use="required" fixed="1.1">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You must include the version number in your GPX document.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="creator" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You must include the name or URL of the software that created your GPX document. This allows others to\r
+ inform the creator of a GPX instance document that fails to validate.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="metadataType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Information about the GPX file, author, and copyright restrictions goes in the metadata section. Providing rich,\r
+ meaningful information about your GPX files allows others to search for and use your GPS data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The name of the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A description of the contents of the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="author" type="personType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The person or organization who created the GPX file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="copyright" type="copyrightType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Copyright and license information governing use of the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ URLs associated with the location described in the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The creation date of the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="keywords" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Keywords associated with the file. Search engines or databases can use this information to classify the data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="bounds" type="boundsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Minimum and maximum coordinates which describe the extent of the coordinates in the file.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="wptType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ wpt represents a waypoint, point of interest, or named feature on a map.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <!-- Position info -->\r
+ <xsd:element name="ele" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Elevation (in meters) of the point.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Creation/modification timestamp for element. Date and time in are in Univeral Coordinated Time (UTC), not local time! Conforms to ISO 8601 specification for date/time representation. Fractional seconds are allowed for millisecond timing in tracklogs. \r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="magvar" type="degreesType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Magnetic variation (in degrees) at the point\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="geoidheight" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Height (in meters) of geoid (mean sea level) above WGS84 earth ellipsoid. As defined in NMEA GGA message.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <!-- Description info -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The GPS name of the waypoint. This field will be transferred to and from the GPS. GPX does not place restrictions on the length of this field or the characters contained in it. It is up to the receiving application to validate the field before sending it to the GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS waypoint comment. Sent to GPS as comment. \r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A text description of the element. Holds additional information about the element intended for the user, not the GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data. "Garmin eTrex", "USGS quad Boston North", e.g.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to additional information about the waypoint.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="sym" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text of GPS symbol name. For interchange with other programs, use the exact spelling of the symbol as displayed on the GPS. If the GPS abbreviates words, spell them out.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of the waypoint.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <!-- Accuracy info -->\r
+ <xsd:element name="fix" type="fixType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type of GPX fix.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="sat" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Number of satellites used to calculate the GPX fix.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="hdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Horizontal dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="vdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Vertical dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="pdop" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Position dilution of precision.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="ageofdgpsdata" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Number of seconds since last DGPS update.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="dgpsid" type="dgpsStationType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ ID of DGPS station used in differential correction.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+\r
+ <xsd:attribute name="lat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="lon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="rteType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ rte represents route - an ordered list of waypoints representing a series of turn points leading to a destination.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS name of route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS comment for route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text description of route for user. Not sent to GPS.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Links to external information about the route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS route number.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of route.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ \r
+ <xsd:element name="rtept" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A list of route points.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="trkType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ trk represents a track - an ordered list of points describing a path.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS name of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="cmt" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS comment for track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="desc" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ User description of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="src" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Source of data. Included to give user some idea of reliability and accuracy of data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Links to external information about track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="number" type="xsd:nonNegativeInteger" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ GPS track number.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type (classification) of track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ \r
+ <xsd:element name="trkseg" type="trksegType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+ \r
+ <xsd:complexType name="extensionsType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence>\r
+ <xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:any>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="trksegType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Segment holds a list of Track Points which are logically connected in order. To represent a single GPS track where GPS reception was lost, or the GPS receiver was turned off, start a new Track Segment for each continuous span of track data.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="trkpt" type="wptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A Track Point holds the coordinates, elevation, timestamp, and metadata for a single point in a track.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+\r
+ <xsd:element name="extensions" type="extensionsType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ You can add extend GPX by adding your own elements from another schema here.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="copyrightType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Information about the copyright holder and any license governing use of this file. By linking to an appropriate license,\r
+ you may place your data into the public domain or grant additional usage rights.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="year" type="xsd:gYear" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Year of copyright.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="license" type="xsd:anyURI" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to external file containing license text.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="author" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Copyright holder (TopoSoft, Inc.)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="linkType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A link to an external resource (Web page, digital photo, video clip, etc) with additional information.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="text" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Text of hyperlink.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="type" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Mime type of content (image/jpeg)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="href" type="xsd:anyURI" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ URL of hyperlink.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="emailType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ An email address. Broken into two parts (id and domain) to help prevent email harvesting.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:attribute name="id" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ id half of email address (billgates2004)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="domain" type="xsd:string" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ domain half of email address (hotmail.com)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="personType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A person or organization.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="name" type="xsd:string" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Name of person or organization.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="email" type="emailType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Email address.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="link" type="linkType" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Link to Web site or other external information about person.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="ptType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ A geographic point with optional elevation and time. Available for use by other schemas.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="ele" type="xsd:decimal" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The elevation (in meters) of the point.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ <xsd:element name="time" type="xsd:dateTime" minOccurs="0">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The time that the point was recorded.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="lat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="lon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="ptsegType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ An ordered sequence of points. (for polygons or polylines, e.g.)\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:sequence> <!-- elements must appear in this order -->\r
+ <xsd:element name="pt" type="ptType" minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Ordered list of geographic points.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+\r
+ <xsd:complexType name="boundsType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Two lat/lon pairs defining the extent of an element.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:attribute name="minlat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The minimum latitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="minlon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The minimum longitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="maxlat" type="latitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The maximum latitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="maxlon" type="longitudeType" use="required">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The maximum longitude.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+\r
+\r
+ <xsd:simpleType name="latitudeType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The latitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="-90.0"/>\r
+ <xsd:maxInclusive value="90.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="longitudeType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ The longitude of the point. Decimal degrees, WGS84 datum.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="-180.0"/>\r
+ <xsd:maxExclusive value="180.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="degreesType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Used for bearing, heading, course. Units are decimal degrees, true (not magnetic).\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:decimal">\r
+ <xsd:minInclusive value="0.0"/>\r
+ <xsd:maxExclusive value="360.0"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="fixType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Type of GPS fix. none means GPS had no fix. To signify "the fix info is unknown, leave out fixType entirely. pps = military signal used\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="none"/>\r
+ <xsd:enumeration value="2d"/>\r
+ <xsd:enumeration value="3d"/>\r
+ <xsd:enumeration value="dgps"/>\r
+ <xsd:enumeration value="pps"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+ <xsd:simpleType name="dgpsStationType">\r
+ <xsd:annotation>\r
+ <xsd:documentation>\r
+ Represents a differential GPS station.\r
+ </xsd:documentation>\r
+ </xsd:annotation>\r
+ <xsd:restriction base="xsd:integer">\r
+ <xsd:minInclusive value="0"/>\r
+ <xsd:maxInclusive value="1023"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+\r
+</xsd:schema>\r
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.utils;
+
+import java.awt.Container;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.sun.image.codec.jpeg.JPEGCodec;
+import com.sun.image.codec.jpeg.JPEGEncodeParam;
+import com.sun.image.codec.jpeg.JPEGImageDecoder;
+import com.sun.image.codec.jpeg.JPEGImageEncoder;
+
+/**
+ * Utility functions for processing JPEG images.
+ */
+public class JpegUtils {
+ /**
+ * Scales an image preserving the aspect ratio.
+ *
+ * @param aMaxWidth Maximum width.
+ * @param aMaxHeight Maximum height.
+ * @param aImage Image to scale.
+ * @return Scaled image.
+ */
+ public static BufferedImage scaleImage(int aMaxWidth, int aMaxHeight, Image aImage) {
+ double thumbRatio = (double) aMaxWidth / (double) aMaxHeight;
+ int imageWidth = aImage.getWidth(null);
+ int imageHeight = aImage.getHeight(null);
+ double imageRatio = (double) imageWidth / (double) imageHeight;
+ if (thumbRatio < imageRatio) {
+ aMaxHeight = (int) (aMaxWidth / imageRatio);
+ } else {
+ aMaxWidth = (int) (aMaxHeight * imageRatio);
+ }
+ BufferedImage thumbImage = new BufferedImage(aMaxWidth, aMaxHeight,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics2D = thumbImage.createGraphics();
+ graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ graphics2D.drawImage(aImage, 0, 0, aMaxWidth, aMaxHeight, null);
+ return thumbImage;
+ }
+
+ /**
+ * Loads a jpeg image from an input stream.
+ *
+ * @param aInput Input stream.
+ * @return JPEG image.
+ * @throws IOException In case of IO problems.
+ * @throws InterruptedException When execution is interrupted.
+ */
+ public static BufferedImage loadJpegImage(InputStream aInput) throws IOException, InterruptedException {
+ JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(aInput);
+ BufferedImage image = decoder.decodeAsBufferedImage();
+ MediaTracker mediaTracker = new MediaTracker(new Container());
+ mediaTracker.addImage(image, 0);
+ mediaTracker.waitForID(0);
+ return image;
+ }
+
+ /**
+ * Writes a JPEG image.
+ *
+ * @param aOutput Output stream to write to.
+ * @param aQuality Quality of the JPEG image in the range 0..100
+ * @param aThumbImage
+ * @throws IOException
+ */
+ public static void writeJpegImage(OutputStream aOutput, int aQuality, BufferedImage aThumbImage) throws IOException {
+ // save thumbnail image to OUTFILE
+
+ if ( aQuality < 0 || aQuality > 100 ) {
+ throw new IllegalArgumentException("Argument quality must be in range 0.100: " + aQuality);
+ }
+
+ JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(aOutput);
+ JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(aThumbImage);
+ aQuality = Math.max(0, Math.min(aQuality, 100));
+ param.setQuality((float) aQuality / 100.0f, false);
+ encoder.setJPEGEncodeParam(param);
+ encoder.encode(aThumbImage);
+ }
+
+}
+
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-ear</artifactId>
+ <packaging>ear</packaging>
+ <name>wamblee.org mythtv directory monitor EAR</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-war</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-monitor</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-timer</artifactId>
+ <version>${project.version}</version>
+ <type>ejb</type>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-ear-plugin</artifactId>
+ <configuration>
+ <defaultJavaBundleDir>lib/</defaultJavaBundleDir>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-monitor</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org mythtv directory monitor MONITOR</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-jpa</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>concurrent</groupId>
+ <artifactId>concurrent</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javaee</groupId>
+ <artifactId>javaee-api</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import javax.annotation.Resource;
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.general.BeanKernel;
+
+/**
+ *
+ */
+public class Application implements ServletContextListener {
+ private static final Log LOG = LogFactory.getLog(Application.class);
+
+ @Resource(name = "MythtvConnectionFactory")
+ private ConnectionFactory connectionFactory;
+
+ @Resource(name = "MythtvTimer")
+ private Queue timerQueue;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
+ */
+ public void contextInitialized(ServletContextEvent arg0) {
+ LOG.info("initializing");
+
+ // Get application configuration.
+ ScheduleConfig config = BeanKernel.getBeanFactory().find(
+ ScheduleConfig.class);
+
+ // Send object message to the timer with the timer interval.
+ try {
+ Connection connection = connectionFactory.createConnection();
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ ObjectMessage msg = session.createObjectMessage();
+ msg.setObject(config.getIntervalSeconds());
+ MessageProducer producer = session.createProducer(timerQueue);
+ producer.send(msg);
+ } catch (Exception e) {
+ LOG.fatal("Error sending message", e);
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
+ */
+ public void contextDestroyed(ServletContextEvent arg0) {
+ LOG.info("terminating");
+
+ // TODO check if timer will be automatically stopped.
+ // Empty.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ *
+ */
+@Entity
+@Table(name="channel")
+public class Channel {
+
+ private int _id;
+
+ private String _name;
+
+ protected Channel() {
+ // Empty
+ }
+
+ /**
+ * @return the id
+ */
+ @Id
+ @Column(name="chanid")
+ public int getId() {
+ return _id;
+ }
+
+ /**
+ * @param aId the id to set
+ */
+ public void setId(int aId) {
+ _id = aId;
+ }
+
+ /**
+ * @return the name
+ */
+ @Column(name="name")
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * @param aName the name to set
+ */
+ public void setName(String aName) {
+ _name = aName;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Channel(" + _id + "," + _name + ")";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObj) {
+ if ( !(aObj instanceof Channel)) {
+ return false;
+ }
+ Channel recording = (Channel)aObj;
+ return _id == recording._id;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _id;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+/**
+ *
+ */
+public enum FileType {
+
+ MPG, AVI;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.SimpleProcess;
+import org.wamblee.io.DirectoryMonitor.Listener;
+
+/**
+ * Link structure.
+ */
+public class LinkStructure implements Listener {
+
+ private static final Log LOG = LogFactory.getLog(LinkStructure.class);
+
+ private String _monitorDir;
+
+ private File _linkDir;
+
+ private RecordingDatabase _database;
+
+ private SimpleDateFormat _format;
+
+ private Map<File,Recording> _recordings;
+
+ public LinkStructure(String aMonitorDir, File aLinkDir,
+ RecordingDatabase aDatabase) {
+ _monitorDir = aMonitorDir + "/";
+ deleteDir(aLinkDir);
+ _linkDir = aLinkDir;
+ _database = aDatabase;
+ _format = new SimpleDateFormat("yyyy-MM-dd-HH:mm");
+ _recordings = new HashMap<File,Recording>();
+ }
+
+ private void deleteDir(File aFile) {
+ for (File file: aFile.listFiles()) {
+ if ( file.isDirectory()) {
+ deleteDir(file);
+ }
+ LOG.info("File deleted " + file + ": " + file.delete());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.DirectoryMonitor.Listener#fileChanged(java.io.File)
+ */
+ public void fileChanged(File aFile) {
+ LOG.debug("file changed " + aFile);
+
+ // Re-assess file type
+ Recording recording = _recordings.get(aFile);
+ LOG.debug("Recording changed " + recording);
+ recording.setFilesize(aFile.length());
+ _database.update(recording);
+ String dir = getDirectory(recording);
+ FileType type = getFileType(aFile);
+ String path = dir + "/" + getFilename(recording, type);
+
+ if (exists(dir + "/" + getFilename(recording, type))) {
+ // Nothing to do.
+ } else {
+ mkdir(dir);
+ for (FileType t : FileType.values()) {
+ rmlink(dir + "/" + getFilename(recording, t));
+ }
+ createSymLink(_monitorDir + aFile.getName(), dir + "/"
+ + getFilename(recording, type));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.DirectoryMonitor.Listener#fileCreated(java.io.File)
+ */
+ public void fileCreated(File aFile) {
+ LOG.debug("file created " + aFile);
+ Recording recording = _database.findRecording(aFile.getName());
+ if ( recording == null ) {
+ LOG.warn("Spurious recording which should not exist according to mythtv: " + aFile);
+ return;
+ }
+ _recordings.put(aFile, recording);
+ LOG.info("New recording detected " + aFile + " "
+ + recording);
+
+ recording.setFilesize(aFile.length());
+ _database.update(recording);
+ String dir = getDirectory(recording);
+ mkdir(dir);
+ createSymLink(_monitorDir + aFile.getName(), dir + "/"
+ + getFilename(recording, getFileType(aFile)));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.DirectoryMonitor.Listener#fileDeleted(java.io.File)
+ */
+ public void fileDeleted(File aFile) {
+ LOG.debug("file deleted " + aFile);
+ Recording recording = _recordings.get(aFile);
+ _recordings.remove(recording);
+ // mythtv will remove the recording from its database itself.
+ LOG.info("recording deleted " + recording);
+ String dir = getDirectory(recording);
+ for (FileType t: FileType.values()) {
+ rmlink(dir + "/" + getFilename(recording, t));
+ }
+ rmdir(dir);
+ }
+
+ private String getDirectory(Recording aRecording) {
+ return aRecording.getTitle().replaceAll("/", "-");
+ }
+
+ private FileType getFileType(File aFile) {
+ SimpleProcess process = new SimpleProcess(new File(_monitorDir), new String[] {
+ "file", aFile.getName() });
+ try {
+ process.run();
+ if (process.getStdout().contains("RIFF")) {
+ return FileType.AVI;
+ } else {
+ return FileType.MPG;
+ }
+ } catch (IOException e) {
+ LOG.error("Determining filetype for " + aFile + " failed", e);
+ return FileType.MPG;
+ }
+ }
+
+ private String getFilename(Recording aRecording, FileType aType) {
+ return (_format.format(aRecording.getProgstart()) + "-"
+ + aRecording.getSubtitle() + "-"
+ + aRecording.getChannel().getName() + "."
+ + aType.toString().toLowerCase()).replaceAll("/", "-");
+ }
+
+ private boolean exists(String aPath) {
+ LOG.debug("exists " + aPath);
+ return new File(_linkDir, aPath).exists();
+ }
+
+ private void rmlink(String aPath) {
+ LOG.debug("rmlink " + aPath);
+ File link = new File(_linkDir, aPath);
+ //if ( !link.exists()) {
+ // return;
+ // }
+ if (!link.delete()) {
+ LOG.warn("Delete failed: " + aPath);
+ } else {
+ LOG.info("Removed link " + link);
+ }
+ }
+
+ private void mkdir(String aDir) {
+ LOG.debug("mkdir " + aDir);
+ File dir = new File(_linkDir, aDir);
+ if ( dir.isDirectory()) {
+ return;
+ }
+ if (!dir.mkdirs()) {
+ LOG.warn("Could not create directory path: " + aDir);
+ } else {
+ LOG.info("Created directory " + dir);
+ }
+ }
+
+ private void rmdir(String aDir) {
+ LOG.debug("rmdir " + aDir);
+ File dir = new File(_linkDir, aDir);
+ if (!dir.delete()) {
+ LOG.warn("Directory not deleted (still recordings left): " + aDir);
+ } else {
+ LOG.info("Directory " + dir + " deleted.");
+ }
+ }
+
+ private void createSymLink(String aTarget, String aSource) {
+ try {
+ SimpleProcess process = new SimpleProcess(_linkDir, new String[] {
+ "ln", "-s", aTarget, aSource });
+ process.run();
+ LOG.info("Created symlink " + aSource + " -> " + aTarget);
+ } catch (IOException e) {
+ LOG.error(
+ "Could not create symlink: " + aTarget + " <- " + aSource,
+ e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+import org.wamblee.general.SpringBeanFactory;
+
+
+/**
+ * Bean factory for the crawler application.
+ */
+public class MythtvBeanFactory extends SpringBeanFactory {
+ private static final String SELECTOR_NAME = "beanRefContext.xml";
+ private static final String FACTORY_NAME = "mythtv";
+
+ /**
+ * Constructs the bean factory.
+ *
+ */
+ public MythtvBeanFactory() {
+ super(SELECTOR_NAME, FACTORY_NAME);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ *
+ */
+public class MythtvHibernateMappings extends HibernateMappingFiles {
+
+ public MythtvHibernateMappings() {
+ super(new String[] { "Channel.hbm.xml", "Recording.hbm.xml" });
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinColumns;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+
+/**
+ *
+ */
+@Entity
+@Table(name="recorded")
+public class Recording implements Serializable {
+
+ private RecordingPk _id;
+
+ private String _basename;
+
+ private Date _progstart;
+
+ private String _title;
+
+ private String _subtitle;
+
+ private long _filesize;
+
+ protected Recording() {
+ // Empty
+ }
+
+ /**
+ * @return the id
+ */
+ @EmbeddedId
+ public RecordingPk getId() {
+ return _id;
+ }
+
+ /**
+ * @param aId the id to set
+ */
+ public void setId(RecordingPk aId) {
+ _id = aId;
+ }
+
+ /**
+ * @return the basename
+ */
+ @Column(name="basename")
+ public String getBasename() {
+ return _basename;
+ }
+
+ /**
+ * @param aBasename the basename to set
+ */
+ public void setBasename(String aBasename) {
+ _basename = aBasename;
+ }
+
+ /**
+ * @return the progstart
+ */
+ @Column(name="progstart")
+ @Temporal(TemporalType.TIMESTAMP)
+ public Date getProgstart() {
+ return _progstart;
+ }
+
+ /**
+ * @param aProgstart the progstart to set
+ */
+ public void setProgstart(Date aProgstart) {
+ _progstart = aProgstart;
+ }
+
+ @Transient
+ public Channel getChannel() {
+ return _id.getChannel();
+ }
+
+ @Transient
+ public Date getStarttime() {
+ return _id.getStartTime();
+ }
+
+ /**
+ * @return the title
+ */
+ @Column(name="title")
+ public String getTitle() {
+ return _title;
+ }
+
+ /**
+ * @param aTitle the title to set
+ */
+ public void setTitle(String aTitle) {
+ _title = aTitle;
+ }
+
+ /**
+ * @return the subtitle
+ */
+ @Column(name="subtitle")
+ public String getSubtitle() {
+ return _subtitle;
+ }
+
+ /**
+ * @param aSubtitle the subtitle to set
+ */
+ public void setSubtitle(String aSubtitle) {
+ _subtitle = aSubtitle;
+ }
+
+ /**
+ * @return the filesize
+ */
+ @Column(name="filesize")
+ public long getFilesize() {
+ return _filesize;
+ }
+
+ public void setFilesize(long aFilesize) {
+ _filesize = aFilesize;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Recording(" + _id + "," + _basename + "," + _progstart + "," + _title + "," + _subtitle + ")";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObj) {
+ if ( !(aObj instanceof Recording)) {
+ return false;
+ }
+ Recording recording = (Recording)aObj;
+ return _id.equals(recording._id);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _id.hashCode();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.criterion.Expression;
+import org.springframework.orm.hibernate3.HibernateCallback;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+import org.springframework.orm.jpa.JpaTemplate;
+import org.springframework.orm.jpa.support.JpaDaoSupport;
+
+/**
+ *
+ */
+public class RecordingDatabase extends JpaDaoSupport {
+
+ private static final Log LOG = LogFactory.getLog(RecordingDatabase.class);
+
+ public RecordingDatabase() {
+ // Empty
+ }
+
+ public void init() {
+ /*
+ for (Recording recording: (List<Recording>)getHibernateTemplate().loadAll(Recording.class) ) {
+ LOG.info("Found recording " + recording);
+ }
+ LOG.info("After listing recordings");
+ */
+ }
+
+ public Recording findRecording(final String aName) {
+ JpaTemplate jpaTemplate = getJpaTemplate();
+ EntityManager entityManager = jpaTemplate.getEntityManager();
+ Query query = entityManager.createQuery(
+ "select r from Recording r where r.basename = ?1");
+ query.setParameter(1, aName);
+ List<Recording> result = query.getResultList();
+ if ( result.size() > 1 ) {
+ throw new RuntimeException("More than two recordings returned");
+ }
+ if ( result.size() == 0 ) {
+ return null;
+ }
+ return result.get(0);
+ }
+
+ public void update(Recording aRecording) {
+ // Update is not required since the whole task of updating the
+ // directory structure occurs within a single transaction.
+ // Therefore, modifications to recordings are automatically persisted.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+/**
+ *
+ */
+@Embeddable
+public class RecordingPk implements Serializable {
+ public Channel _channel;
+ public Date _starttime;
+
+ public RecordingPk() {
+ // Empty
+ }
+
+ /**
+ * @return the channel
+ */
+ @ManyToOne
+ @JoinColumn(name="chanid")
+ public Channel getChannel() {
+ return _channel;
+ }
+
+ /**
+ * @param aChannel the channel to set
+ */
+ public void setChannel(Channel aChannel) {
+ _channel = aChannel;
+ }
+
+ /**
+ * @return the starttime
+ */
+ @Column(name="starttime")
+ @Temporal(TemporalType.TIMESTAMP)
+ public Date getStartTime() {
+ return _starttime;
+ }
+
+ /**
+ * @param aStarttime the starttime to set
+ */
+ public void setStartTime(Date aStarttime) {
+ _starttime = aStarttime;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObj) {
+ if ( aObj == null ) {
+ return false;
+ }
+ if ( !(aObj instanceof RecordingPk)) {
+ return false;
+ }
+ RecordingPk pk = (RecordingPk) aObj;
+ return _channel.equals(pk._channel) && _starttime.equals(pk._starttime);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _channel.hashCode() + _starttime.hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.mythtv;
+
+/**
+ *
+ */
+public class ScheduleConfig {
+
+ private int _intervalSeconds;
+
+ public ScheduleConfig(int aIntervalSeconds) {
+ _intervalSeconds = aIntervalSeconds;
+ }
+
+ /**
+ * @return the intervalSeconds
+ */
+ public int getIntervalSeconds() {
+ return _intervalSeconds;
+ }
+
+}
--- /dev/null
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+ version="1.0">
+ <persistence-unit name="mythtv">
+ <provider>org.hibernate.ejb.HibernatePersistence</provider>
+ <jta-data-source>jdbc/mythtv</jta-data-source>
+ <class>org.wamblee.mythtv.Channel</class>
+ <class>org.wamblee.mythtv.Recording</class>
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+ <!-- properties>
+ <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
+ <property name="hibernate.show_sql" value="false"/>
+ <property name="hibernate.cache.provider" value="org.hibernate.cache.EhCacheProvider"/>
+ </properties -->
+ </persistence-unit>
+</persistence>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
+ "http://www.springframework.org/dtd/spring-beans.dtd">
+<beans>
+
+ <bean id="dataSource"
+ class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+ <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
+ <property name="url"><value>jdbc:mysql://10.0.0.140/mythconverg</value></property>
+ <property name="username"><value>mythtv</value></property>
+ <property name="password"><value>mythtv</value></property>
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv</artifactId>
+ <packaging>pom</packaging>
+ <name>wamblee.org mythtv directory monitor</name>
+ <url>http://wamblee.org</url>
+
+ <modules>
+ <module>monitor</module>
+ <module>war</module>
+ <module>timer</module>
+ <module>ear</module>
+ </modules>
+
+ <dependencies>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <webXml>${basedir}/src/webapp/WEB-INF/web.xml</webXml>
+ <warName>wamblee-mythtv</warName>
+ <warSourceDirectory>src/webapp</warSourceDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-timer</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org mythtv directory monitor TIMER</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-monitor</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javaee</groupId>
+ <artifactId>javaee-api</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.timer;
+
+import javax.annotation.Resource;
+import javax.ejb.MessageDriven;
+import javax.ejb.Timeout;
+import javax.ejb.Timer;
+import javax.ejb.TimerService;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.ejb.TransactionManagement;
+import javax.ejb.TransactionManagementType;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.ObjectMessage;
+import javax.jms.StreamMessage;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.io.DirectoryMonitor;
+
+/**
+ *
+ */
+@MessageDriven(name = "MythtvTimer")
+// Spring's JTA transaction manager does not work with container managed transactions
+// because it uses the UserTransaction object which glassfish forbids.
+@TransactionManagement(TransactionManagementType.BEAN)
+public class TimerBean implements MessageListener {
+
+ private static final Log LOG = LogFactory.getLog(TimerBean.class);
+
+ @Resource
+ private TimerService _timerService;
+
+ /**
+ * Initialization of the time interval. The initialization is done through a
+ * StreamMessage with a single integer containing the time interval to use.
+ */
+ public void onMessage(Message aInitMessage) {
+ ObjectMessage msg = (ObjectMessage) aInitMessage;
+ try {
+ int interval = (Integer)msg.getObject();
+ LOG.info("Initializing timer with interval " + interval + " seconds");
+ _timerService.createTimer(interval*1000, interval*1000, null);
+ } catch (JMSException e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ @Timeout
+ private void timeout(Timer aTimer) {
+ LOG.debug("Timer expired!!!");
+ try {
+ DirectoryMonitor monitor = BeanKernel.getBeanFactory().find(
+ DirectoryMonitor.class);
+ monitor.poll();
+ } catch (Throwable t) {
+ LOG
+ .error(
+ "something terrible happend, ignoring it and hoping for the best",
+ t);
+ }
+ }
+}
--- /dev/null
+<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+ http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
+ version="3.0">
+
+ <!-- enterprise-beans>
+ <message-driven>
+ <ejb-name>TimerBean</ejb-name>
+ <ejb-class>org.wamblee.timer.TimerBean</ejb-class>
+ <message-destination-ref>
+ <message-destination-ref-name>MythtvTimer</message-destination-ref-name>
+ </message-destination-ref>
+ </message-driven>
+ </enterprise-beans -->
+ <!-- assembly-descriptor>
+ <message-destination>
+
+ <message-destination-name>MythtvTimer</message-destination-name>
+ <mapped-name>jms/MythtvTimer</mapped-name>
+ </message-destination>
+ </assembly-descriptor -->
+
+</ejb-jar>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">
+<sun-ejb-jar>
+
+ <enterprise-beans>
+ <ejb>
+ <ejb-name>MythtvTimer</ejb-name>
+ <jndi-name>jms/MythtvTimer</jndi-name>
+ </ejb>
+ </enterprise-beans>
+
+</sun-ejb-jar>
\ No newline at end of file
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-war</artifactId>
+ <packaging>war</packaging>
+ <name>wamblee.org mythtv directory monitor WAR</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-mythtv-monitor</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javaee</groupId>
+ <artifactId>javaee-api</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <webXml>${basedir}/src/webapp/WEB-INF/web.xml</webXml>
+ <warName>wamblee-mythtv</warName>
+ <warSourceDirectory>src/webapp</warSourceDirectory>
+ <warSourceExcludes>WEB-INF/lib/*.jar</warSourceExcludes>
+ <archive>
+ <manifest>
+ <addClasspath>true</addClasspath>
+ <classpathPrefix>lib</classpathPrefix>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping default-access="field">
+
+ <class name="org.wamblee.mythtv.Recording" table="recorded" lazy="false">
+
+ <composite-id>
+ <key-many-to-one name="_channel" column="chanid" class="org.wamblee.mythtv.Channel"/>
+ <key-property name="_starttime" column="starttime"></key-property>
+ </composite-id>
+
+ <property name="_basename" column="basename"/>
+ <property name="_progstart" column="progstart"/>
+ <property name="_title" column="title"/>
+ <property name="_subtitle" column="subtitle"/>
+ <property name="_filesize" column="filesize"/>
+ </class>
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="mythtv"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org.wamblee.mythtv.properties.xml</value>
+ <value>org.wamblee.mythtv.datasource.xml</value>
+ <value>org.wamblee.mythtv.hibernate.xml</value>
+ <value>org.wamblee.mythtv.application.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+##############################################################################
+# Class name of the beanfactory used by the crawler application
+##############################################################################
+
+org.wamblee.beanfactory.class=org.wamblee.mythtv.MythtvBeanFactory
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="linkDir" class="java.io.File">
+ <constructor-arg>
+ <value>${org.wamblee.mythtv.linkdir}</value>
+ </constructor-arg>
+ </bean>
+
+ <bean id="monitorDir" class="java.io.File">
+ <constructor-arg>
+ <value>${org.wamblee.mythtv.monitordir}</value>
+ </constructor-arg>
+ </bean>
+
+ <bean id="fileFilter" class="org.apache.oro.io.AwkFilenameFilter">
+ <constructor-arg>
+ <value>^[a-zA-Z0-9-_]*.mpg$</value>
+ </constructor-arg>
+ </bean>
+
+
+ <bean id="org.wamblee.mythtv.RecordingDatabase" class="org.wamblee.mythtv.RecordingDatabase"
+ init-method="init">
+ <property name="entityManager">
+ <ref bean="entityManager"/>
+ </property>
+ </bean>
+
+ <bean id="org.wamblee.mythtv.LinkStructure"
+ parent="transactionRequiredTemplate">
+ <property name="target">
+ <bean class="org.wamblee.mythtv.LinkStructure">
+ <constructor-arg>
+ <value>${org.wamblee.mythtv.monitordir}</value>
+ </constructor-arg>
+ <constructor-arg>
+ <ref local="linkDir"/>
+ </constructor-arg>
+ <constructor-arg>
+ <ref local="org.wamblee.mythtv.RecordingDatabase"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="org.wamblee.io.DirectoryMonitor" class="org.wamblee.io.DirectoryMonitor">
+ <constructor-arg>
+ <ref local="monitorDir"/>
+ </constructor-arg>
+ <constructor-arg>
+ <ref local="fileFilter"/>
+ </constructor-arg>
+ <constructor-arg>
+ <ref local="org.wamblee.mythtv.LinkStructure"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.mythtv.ScheduleConfig" class="org.wamblee.mythtv.ScheduleConfig">
+ <constructor-arg>
+ <value>${org.wamblee.mythtv.pollinterval}</value>
+ </constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
+ <property name="jndiName" value="${org.wamblee.mythtv.datasource}"/>
+ </bean>
+</beans>
--- /dev/null
+
+###################################################################################
+# dialect
+###################################################################################
+hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
+
+###################################################################################
+# debugging settings: Log4j configuration can provide more detail.
+###################################################################################
+hibernate.show_sql=false
+
+###################################################################################
+# hibernate cache provider
+###################################################################################
+hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
+
+###################################################################################
+# query cache
+###################################################################################
+hibernate.cache.use_query_cache=true
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <!-- bean id="namingStrategy" class="nl.jmonks.system.hibernate.JMonksNamingStrategy"> </bean -->
+
+ <!-- bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
+ <property name="dataSource">
+ <ref bean="dataSource"/>
+ </property>
+ <property name="hibernateProperties">
+ <props>
+ <prop key="hibernate.dialect">${hibernate.dialect}</prop>
+ <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
+ <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
+ <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
+ <prop key="hibernate.query.substitutions">true 1, false 0</prop>
+ </props>
+ </property>
+ <property name="annotatedClasses">
+ <list>
+ <value>org.wamblee.mythtv.Channel</value>
+ <value>org.wamblee.mythtv.Recording</value>
+ </list>
+ </property>
+
+ </bean -->
+
+ <!-- bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
+ <property name="sessionFactory">
+ <ref bean="sessionFactory"/>
+ </property>
+ </bean -->
+
+ <!-- bean id="transactionManager"
+ class="org.springframework.orm.hibernate3.HibernateTransactionManager">
+ <property name="sessionFactory">
+ <ref local="sessionFactory"/>
+ </property>
+ </bean -->
+
+ <bean id="entityManager" class="org.springframework.jndi.JndiObjectFactoryBean">
+ <property name="jndiName"><value>java:comp/env/persistence/mythtv</value></property>
+ </bean>
+
+ <bean id="transactionManager"
+ class="org.springframework.transaction.jta.JtaTransactionManager">
+
+ </bean>
+
+ <!-- Abstract bean. Subclass this bean and specify the target property to
+ wrap a bean with transactions -->
+ <bean abstract="true" id="transactionRequiredTemplate"
+ class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
+ <property name="transactionManager" ref="transactionManager"/>
+ <property name="transactionAttributes">
+ <props>
+ <prop key="*">PROPAGATION_REQUIRED</prop>
+ </props>
+ </property>
+ </bean>
+
+
+</beans>
--- /dev/null
+
+org.wamblee.mythtv.datasource=jdbc/mythtv
+org.wamblee.mythtv.pollinterval=2
+#org.wamblee.mythtv.monitordir=/data/vcr
+#org.wamblee.mythtv.linkdir=/data/vcr/links
+
+org.wamblee.mythtv.monitordir=/ext/home/erik/java/workspace/utils/mythtv/testdata/input
+org.wamblee.mythtv.linkdir=/ext/home/erik/java/workspace/utils/mythtv/testdata/links
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="propertyBean"
+ class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+ <property name="locations">
+ <list>
+ <value>org.wamblee.mythtv.hibernate.properties</value>
+ <value>org.wamblee.mythtv.properties</value>
+ </list>
+ </property>
+ </bean>
+ </beans>
\ No newline at end of file
--- /dev/null
+Manifest-Version: 1.0\r
+Class-Path: \r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0
+Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">
+
+
+<sun-web-app>
+
+ <resource-ref>
+ <res-ref-name>MythtvConnectionFactory</res-ref-name>
+ <jndi-name>jms/MythtvConnectionFactory</jndi-name>
+ </resource-ref>
+
+
+ <message-destination-ref>
+ <message-destination-ref-name>MythtvTimer</message-destination-ref-name>
+ <jndi-name>jms/MythtvTimer</jndi-name>
+ </message-destination-ref>
+
+</sun-web-app>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5"
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+ http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+ <listener>
+ <listener-class>org.wamblee.mythtv.Application</listener-class>
+ </listener>
+
+ <persistence-context-ref>
+ <persistence-context-ref-name>persistence/mythtv</persistence-context-ref-name>
+ </persistence-context-ref>
+
+ <!-- resource-ref>
+ <res-ref-name>MythtvConnectionFactory</res-ref-name>
+ <res-type>javax.jms.ConnectionFactory</res-type>
+ <res-auth>Container</res-auth>
+ <res-sharing-scope>Shareable</res-sharing-scope>
+ </resource-ref -->
+
+ <!-- message-destination-ref>
+ <message-destination-ref-name>jms/MythtvTimer</message-destination-ref-name>
+ <message-destination-type>javax.jms.Queue</message-destination-type>
+ <message-destination-usage>Produces</message-destination-usage>
+ <message-destination-link>Timer</message-destination-link>
+ </message-destination-ref -->
+
+</web-app>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"\r
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">\r
+ <modelVersion>4.0.0</modelVersion>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-utils</artifactId>\r
+ <packaging>pom</packaging>\r
+ <version>0.2-SNAPSHOT</version>\r
+ <name>wamblee.org utility libraries</name>\r
+ <url>http://wamblee.org</url>\r
+ <modules>\r
+ <module>support</module>\r
+ <module>socketproxy</module>\r
+ <module>crawler</module>\r
+ <module>gps</module>\r
+ <module>mythtv</module>\r
+ </modules>\r
+ <dependencies>\r
+ <dependency>\r
+ <groupId>junit</groupId>\r
+ <artifactId>junit</artifactId>\r
+ <version>3.8.1</version>\r
+ <scope>test</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>dbunit</groupId>\r
+ <artifactId>dbunit</artifactId>\r
+ <version>2.1</version>\r
+ <scope>test</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>jmock</groupId>\r
+ <artifactId>jmock-cglib</artifactId>\r
+ <version>1.1.0</version>\r
+ <scope>test</scope>\r
+ <exclusions>\r
+ <exclusion>\r
+ <groupId>cglib</groupId>\r
+ <artifactId>cglib-full</artifactId>\r
+ </exclusion>\r
+ </exclusions>\r
+ </dependency>\r
+ </dependencies>\r
+\r
+ <dependencyManagement>\r
+ <dependencies>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-support</artifactId>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-support</artifactId>\r
+ <type>test-jar</type>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-socketproxy</artifactId>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-socketproxy</artifactId>\r
+ <type>test-jar</type>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-crawler</artifactId>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-crawler</artifactId>\r
+ <type>test-jar</type>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-crawler-basic</artifactId>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.wamblee</groupId>\r
+ <artifactId>wamblee-crawler-basic</artifactId>\r
+ <type>test-jar</type>\r
+ <version>${project.version}</version>\r
+ </dependency>\r
+\r
+ <dependency>\r
+ <groupId>javax.servlet</groupId>\r
+ <artifactId>servlet-api</artifactId>\r
+ <version>2.3</version>\r
+ <type>jar</type>\r
+ <scope>provided</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>quartz</groupId>\r
+ <artifactId>quartz</artifactId>\r
+ <version>1.5.1</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>jtidy</groupId>\r
+ <artifactId>jtidy</artifactId>\r
+ <version>4aug2000r7-dev</version>\r
+ </dependency>\r
+\r
+ <dependency>\r
+ <groupId>concurrent</groupId>\r
+ <artifactId>concurrent</artifactId>\r
+ <version>1.3.4</version>\r
+ </dependency>\r
+\r
+ <dependency>\r
+ <groupId>oro</groupId>\r
+ <artifactId>oro</artifactId>\r
+ <version>2.0.6</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>commons-logging</groupId>\r
+ <artifactId>commons-logging</artifactId>\r
+ <version>1.0.2</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>commons-httpclient</groupId>\r
+ <artifactId>commons-httpclient</artifactId>\r
+ <version>3.0</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>commons-beanutils</groupId>\r
+ <artifactId>commons-beanutils</artifactId>\r
+ <version>1.7.0</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-beans</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-web</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-jms</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-context</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-hibernate3</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-jpa</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.springframework</groupId>\r
+ <artifactId>spring-aop</artifactId>\r
+ <version>${springversion}</version>\r
+ </dependency>\r
+\r
+ <!-- should be possible to remove the dependence on log4j -->\r
+ <dependency>\r
+ <groupId>log4j</groupId>\r
+ <artifactId>log4j</artifactId>\r
+ <version>1.2.8</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>dom4j</groupId>\r
+ <artifactId>dom4j</artifactId>\r
+ <version>1.6</version>\r
+ <exclusions>\r
+ <exclusion>\r
+ <groupId>xml-apis</groupId>\r
+ <artifactId>xml-apis</artifactId>\r
+ </exclusion>\r
+ </exclusions>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>net.sf.ehcache</groupId>\r
+ <artifactId>ehcache</artifactId>\r
+ <version>1.2.3</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>xerces</groupId>\r
+ <artifactId>xercesImpl</artifactId>\r
+ <version>2.8.1</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.hibernate</groupId>\r
+ <artifactId>hibernate</artifactId>\r
+ <version>3.2.5.ga</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.hibernate</groupId>\r
+ <artifactId>hibernate-entitymanager</artifactId>\r
+ <version>3.3.1.ga</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>javax.persistence</groupId>\r
+ <artifactId>persistence-api</artifactId>\r
+ <version>1.0</version>\r
+ <scope>provided</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>commons-email</groupId>\r
+ <artifactId>commons-email</artifactId>\r
+ <version>1.0</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>javax.mail</groupId>\r
+ <artifactId>mail</artifactId>\r
+ <version>1.3.3_01</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>jaxen</groupId>\r
+ <artifactId>jaxen</artifactId>\r
+ <version>1.1-beta-9</version>\r
+ <exclusions>\r
+ <exclusion>\r
+ <groupId>xom</groupId>\r
+ <artifactId>xom</artifactId>\r
+ </exclusion>\r
+ <exclusion>\r
+ <groupId>xerces</groupId>\r
+ <artifactId>xmlParserAPIs</artifactId>\r
+ </exclusion>\r
+ </exclusions>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>jstl</groupId>\r
+ <artifactId>jstl</artifactId>\r
+ <version>1.1.2</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>taglibs</groupId>\r
+ <artifactId>standard</artifactId>\r
+ <version>1.1.2</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>jfree</groupId>\r
+ <artifactId>jfreechart</artifactId>\r
+ <version>1.0.1</version>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>jfree</groupId>\r
+ <artifactId>jcommon</artifactId>\r
+ <version>1.0.2</version>\r
+ </dependency>\r
+ \r
+ <dependency>\r
+ <groupId>javaee</groupId>\r
+ <artifactId>javaee-api</artifactId>\r
+ <version>5</version>\r
+ <scope>provided</scope>\r
+ </dependency>\r
+\r
+\r
+ </dependencies>\r
+ </dependencyManagement>\r
+\r
+ <build>\r
+ <plugins>\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-compiler-plugin</artifactId>\r
+ <configuration>\r
+ <source>1.6</source>\r
+ <target>1.6</target>\r
+ </configuration>\r
+ </plugin>\r
+\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-surefire-plugin</artifactId>\r
+ <configuration>\r
+ <includes>\r
+ <include>**/*Test.java</include>\r
+ </includes>\r
+ </configuration>\r
+ </plugin>\r
+\r
+ <!-- Make sure other projects can use (or the test support\r
+ and test classes from the projects it uses. To use\r
+ a dependence on a test library of a project, an additinoal\r
+ dependence must be added with <type>test-jar</type>\r
+ -->\r
+\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-jar-plugin</artifactId>\r
+ <executions>\r
+ <execution>\r
+ <goals>\r
+ <goal>test-jar</goal>\r
+ </goals>\r
+ </execution>\r
+ </executions>\r
+ </plugin>\r
+\r
+ <plugin>\r
+ <groupId>org.codehaus.mojo</groupId>\r
+ <artifactId>cobertura-maven-plugin</artifactId>\r
+ <executions>\r
+ <execution>\r
+ <goals>\r
+ <goal>clean</goal>\r
+ </goals>\r
+ </execution>\r
+ </executions>\r
+ </plugin>\r
+\r
+ </plugins>\r
+\r
+ </build>\r
+\r
+ <reporting>\r
+ <plugins>\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-project-info-reports-plugin</artifactId>\r
+ <reportSets>\r
+ <reportSet>\r
+ <reports>\r
+ <report>checkstyle</report>\r
+ <report>javadoc</report>\r
+ <report>dependencies</report>\r
+ <report>project-team</report>\r
+ <report>mailing-list</report>\r
+ <report>issue-tracking</report>\r
+ <report>license</report>\r
+ <report>scm</report>\r
+ </reports>\r
+ </reportSet>\r
+ </reportSets>\r
+ </plugin>\r
+ <plugin>\r
+ <groupId>org.codehaus.mojo</groupId>\r
+ <artifactId>changes-maven-plugin</artifactId>\r
+ <version>2.0-beta-1</version>\r
+ <reportSets>\r
+ <reportSet>\r
+ <reports>\r
+ <report>changes-report</report>\r
+ </reports>\r
+ </reportSet>\r
+ </reportSets>\r
+ </plugin>\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-javadoc-plugin</artifactId>\r
+ </plugin>\r
+ <plugin>\r
+ <groupId>org.codehaus.mojo</groupId>\r
+ <artifactId>surefire-report-maven-plugin</artifactId>\r
+ </plugin>\r
+ <!-- Test coverage reporting -->\r
+ <plugin>\r
+ <groupId>org.codehaus.mojo</groupId>\r
+ <artifactId>cobertura-maven-plugin</artifactId>\r
+ </plugin>\r
+\r
+ <!-- checkstyle -->\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-checkstyle-plugin</artifactId>\r
+ <configuration>\r
+ <configLocation>config/sun_checks.xml</configLocation>\r
+ </configuration>\r
+ </plugin>\r
+\r
+ <!-- taglist -->\r
+ <plugin>\r
+ <groupId>org.codehaus.mojo</groupId>\r
+ <artifactId>taglist-maven-plugin</artifactId>\r
+ <configuration>\r
+ <tags>\r
+ <tag>TODO</tag>\r
+ <tag>@todo</tag>\r
+ <tag>FIXME</tag>\r
+ </tags>\r
+ </configuration>\r
+ </plugin>\r
+\r
+ </plugins>\r
+ </reporting>\r
+ \r
+ <repositories>\r
+ <repository>\r
+ <id>javaee</id>\r
+ <name>Java EE repo at SUN</name>\r
+ <url>http://download.java.net/maven/1</url>\r
+ <layout>legacy</layout>\r
+ </repository>\r
+ </repositories>\r
+\r
+ <properties>\r
+ <springversion>2.0.8</springversion>\r
+ </properties>\r
+\r
+</project>\r
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-socketproxy</artifactId>
+ <packaging>jar</packaging>
+
+ <name>wamblee.org socket proxy</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.socketproxy;
+
+public class Barrier {
+
+ private int _countLeft;
+
+ public Barrier( int aCount ) {
+ _countLeft = aCount;
+ }
+
+ public synchronized void block( ) {
+ _countLeft--;
+ if ( _countLeft < 0 ) {
+ throw new IllegalStateException(
+ "Barrier count became negative, programming error" );
+ }
+ notifyAll( );
+ while ( _countLeft > 0 ) {
+ waitUninterruptable( );
+ }
+ }
+
+ private void waitUninterruptable( ) {
+ try {
+ wait( );
+ } catch ( InterruptedException e ) {
+ // ignore.
+ }
+ }
+
+}
--- /dev/null
+package org.wamblee.socketproxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Forwarder thread which handles forwarding of an input stream to
+ * an output stream.
+ */
+
+public class ForwarderThread extends Thread {
+
+ private String _prefix;
+
+ private Barrier _barrier;
+
+ private InputStream _is;
+
+ private OutputStream _os;
+
+ /**
+ * Constructs the forwarder thread.
+ * @param aPrefix Prefix to use in the output.
+ * @param aBarrier Barrier to block on before actually closing the stream.
+ * This is done to make sure that connections are only closed in th e
+ * proxy when the forwarders in both directions are ready to close.
+ * @param aIs Input stream to read from.
+ * @param aOs Output stream to forward to.
+ */
+ public ForwarderThread( String aPrefix, Barrier aBarrier,
+ InputStream aIs, OutputStream aOs ) {
+ _prefix = aPrefix;
+ _is = aIs;
+ _os = aOs;
+ _barrier = aBarrier;
+ }
+
+ public void run( ) {
+ boolean firstChar = true;
+ try {
+ int c = _is.read( );
+ while ( c > 0 ) {
+ try {
+ _os.write( c );
+ _os.flush( );
+ if ( firstChar ) {
+ System.out.print( _prefix );
+ firstChar = false;
+ }
+ System.out.print( (char) c );
+ if ( c == '\n' ) {
+ firstChar = true;
+ }
+ } catch ( IOException e ) {
+ }
+
+ c = _is.read( );
+ }
+ } catch ( IOException e ) {
+ }
+ closeStreams();
+ }
+
+ /**
+ * @param is
+ * @param os
+ */
+ private void closeStreams( ) {
+ _barrier.block( ); // wait until the other forwarder for the other direction
+ // is also closed.
+ try {
+ _is.close( );
+ } catch ( IOException e1 ) {
+ // Empty.
+ }
+ try {
+ _os.flush( );
+ _os.close( );
+ } catch ( IOException e1 ) {
+ // Empty
+ }
+ System.out.println(_prefix + " closed");
+ }
+
+}
--- /dev/null
+package org.wamblee.socketproxy;
+
+/*
+ * Created on Apr 5, 2005
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * @author erik
+ *
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class SocketProxy {
+
+ public static void main( final String[] args ) throws IOException {
+ for ( int i = 0; i < args.length; i++ ) {
+ // System.out.println(i + " " + args[i]);
+ String[] fields = args[i].split( ":" );
+ final int localPort = Integer.parseInt( fields[0] );
+ final String host = fields[1];
+ final int remotePort = Integer.parseInt( fields[2] );
+ runSocketProxy( localPort, host, remotePort );
+ }
+ }
+
+ /**
+ * @param localPort
+ * @param host
+ * @param remotePort
+ */
+ private static void runSocketProxy( final int localPort,
+ final String host, final int remotePort ) {
+ new Thread( new Runnable( ) {
+ public void run( ) {
+ try {
+ new SocketProxy( localPort, host, remotePort );
+ } catch ( IOException e ) {
+ System.out.println( "Problem with socket " + localPort
+ + ":" + host + ":" + remotePort );
+ e.printStackTrace( );
+ }
+ }
+ } ).start( );
+ }
+
+ public SocketProxy( int localPort, String remoteHost, int remotePort )
+ throws IOException {
+ System.out.println( "Listening on port " + localPort );
+ ServerSocket server = new ServerSocket( localPort );
+ for ( ;; ) {
+ Socket socket = server.accept( );
+ System.out.println( "Got local connection on port "
+ + localPort );
+ InputStream localIs = socket.getInputStream( );
+ OutputStream localOs = socket.getOutputStream( );
+ Socket clientSocket = new Socket( remoteHost, remotePort );
+ final String description = "Port forwarding: " + localPort
+ + " -> " + remoteHost + ":" + remotePort;
+ System.out.println( description + " established." );
+ InputStream serverIs = clientSocket.getInputStream( );
+ OutputStream serverOs = clientSocket.getOutputStream( );
+ Barrier barrier = new Barrier(2);
+ final Thread t1 = runForwarder( barrier, "> ", localIs, serverOs );
+ final Thread t2 = runForwarder( barrier, "< ", serverIs, localOs );
+ waitForConnectionClose( description, t1, t2 );
+ }
+ }
+
+ /**
+ * @param description
+ * @param t1
+ * @param t2
+ */
+ private void waitForConnectionClose( final String description,
+ final Thread t1, final Thread t2 ) {
+ new Thread( new Runnable( ) {
+ public void run( ) {
+ try {
+ t1.join( );
+ t2.join( );
+ } catch ( InterruptedException e ) {
+ e.printStackTrace( );
+ }
+ System.out.println( description + " closed" );
+ }
+ } ).start( );
+ }
+
+ private Thread runForwarder( final Barrier barrier, final String prefix,
+ final InputStream is, final OutputStream os ) {
+ Thread t = new ForwarderThread(prefix, barrier, is, os);
+ t.start( );
+ return t;
+ }
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support</artifactId>
+ <packaging>jar</packaging>
+ <name>wamblee.org support library</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-hibernate3</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aop</artifactId>
+ </dependency>
+ <!-- should be possible to remove the dependence on log4j -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xercesImpl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>oro</groupId>
+ <artifactId>oro</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * The <code>Cache</code> interface represents... a cache.
+ * In some circumstances it is more optimal to implement caching directly in
+ * the code instead of relying on Hibernate caching methods. This interface abstracts
+ * from the used cache implementation.
+ * Cache implementations must be thread-safe.
+ */
+public interface Cache<KeyType extends Serializable, ValueType extends Serializable> {
+
+ /**
+ * Adds a key-value pair to the cache.
+ * @param aKey Key.
+ * @param aValue Value.
+ */
+ void put(KeyType aKey, ValueType aValue);
+
+ /**
+ * Retrieves a value from the cache.
+ * @param aKey Key to retrieve.
+ * @return Key.
+ */
+ ValueType get(KeyType aKey);
+
+ /**
+ * Removes an entry from the cache.
+ * @param aKey Key to remove the entry for.
+ */
+ void remove(KeyType aKey);
+
+ /**
+ * Removes all entries from the cache.
+ *
+ */
+ void clear();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Represents a cached object. The object is either retrieved from the cache if
+ * the cache has it, or a call back is invoked to get the object (and put it in
+ * the cache).
+ */
+public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
+
+ private static final Logger LOGGER = Logger.getLogger(CachedObject.class);
+
+ /**
+ * Callback invoked to compute an object if it was not found in the cache.
+ *
+ * @param <T>
+ * Type of the object
+ */
+ public static interface Computation<Key extends Serializable, Value extends Serializable> {
+ /**
+ * Gets the object. Called when the object is not in the cache.
+ *
+ * @param aObjectKey
+ * Id of the object in the cache.
+ * @return Object, must be non-null.
+ */
+ Value getObject(Key aObjectKey);
+ }
+
+ /**
+ * Cache to use.
+ */
+ private Cache<KeyType, ValueType> _cache;
+
+ /**
+ * Key of the object in the cache.
+ */
+ private KeyType _objectKey;
+
+ /**
+ * Computation used to obtain the object if it is not found in the cache.
+ */
+ private Computation<KeyType, ValueType> _computation;
+
+ /**
+ * Constructs the cached object.
+ *
+ * @param aCache
+ * Cache to use.
+ * @param aObjectKey
+ * Key of the object in the cache.
+ * @param aComputation
+ * Computation to get the object in case the object is not in the
+ * cache.
+ */
+ public CachedObject(Cache<KeyType, ValueType> aCache, KeyType aObjectKey,
+ Computation<KeyType, ValueType> aComputation) {
+ _cache = aCache;
+ _objectKey = aObjectKey;
+ _computation = aComputation;
+ }
+
+ /**
+ * Gets the object. Since the object is cached, different calls to this
+ * method may return different objects.
+ *
+ * @return Object.
+ */
+ public ValueType get() {
+ ValueType object = (ValueType) _cache.get(_objectKey); // the used
+ // cache is
+ // thread safe.
+ if (object == null) {
+ // synchronize the computation to make sure that the object is only
+ // computed
+ // once when multiple concurrent threads detect that the entry must
+ // be
+ // recomputed.
+ synchronized (this) {
+ object = (ValueType) _cache.get(_objectKey);
+ if (object == null) {
+ // No other thread did a recomputation so we must do this
+ // now.
+ LOGGER.debug("Refreshing cache for '" + _objectKey + "'");
+ object = _computation.getObject(_objectKey);
+ _cache.put(_objectKey, object);
+ }
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Invalidates the cache for the object so that it is recomputed the next
+ * time it is requested.
+ *
+ */
+ public void invalidate() {
+ _cache.remove(_objectKey);
+ }
+
+ /**
+ * Gets the cache.
+ *
+ * @return Cache.
+ */
+ public Cache getCache() {
+ return _cache;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+import org.wamblee.io.InputResource;
+
+/**
+ * Cache implemented on top of EhCache.
+ */
+public class EhCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements org.wamblee.cache.Cache<KeyType, ValueType> {
+
+ private static final Logger LOGGER = Logger.getLogger(EhCache.class);
+
+ /**
+ * EH Cache manager.
+ */
+ private CacheManager _manager;
+
+ /**
+ * EH cache.
+ */
+ private Cache _cache;
+
+ /**
+ * Constructs a cache based on EHCache.
+ *
+ * @param aResource
+ * Resource containing the configuration file for EHCache.
+ * @param aCacheName
+ * Name of the cache to use. If a cache with this name does not
+ * exist, one is created based on default settings.
+ * @throws IOException
+ * @throws CacheException
+ */
+ public EhCache(InputResource aResource, String aCacheName)
+ throws IOException, CacheException {
+ InputStream is = aResource.getInputStream();
+ try {
+ _manager = CacheManager.create(is);
+ _cache = _manager.getCache(aCacheName);
+ if (_cache == null) {
+ LOGGER.warn("Creating cache '" + aCacheName
+ + "' because it is not configured");
+ _manager.addCache(aCacheName);
+ _cache = _manager.getCache(aCacheName);
+ }
+ assert _cache != null;
+ } finally {
+ is.close();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ _cache.put(new Element(aKey, aValue));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ try {
+ Element element = _cache.get(aKey);
+ if (element == null) {
+ return null;
+ }
+ return (ValueType) element.getValue();
+ } catch (CacheException e) {
+ throw new RuntimeException("Cache problem key = '" + aKey + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ _cache.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ _cache.removeAll();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * A very simple cache based on a HashMap, It never expires any entries, and has
+ * no bounds on its size.
+ */
+public class ForeverCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ /**
+ * Cached entries.
+ */
+ private HashMap<KeyType, ValueType> _map;
+
+ /**
+ * Constructs the cache.
+ *
+ */
+ public ForeverCache() {
+ _map = new HashMap<KeyType, ValueType>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public synchronized void put(KeyType aKey, ValueType aValue) {
+ _map.put(aKey, aValue);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public synchronized ValueType get(KeyType aKey) {
+ return _map.get(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public synchronized void remove(KeyType aKey) {
+ _map.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public synchronized void clear() {
+ _map.clear();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * A cache that does not cache. This implementation is useful for disabling caching.
+ * Because of this implementation, application code does not need to distinguish
+ * between the situation where it a cache is used and where it isn't.
+ */
+public class ZeroCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ public ZeroCache() {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ // Empty
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ // Empty
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides an interface for a cache together with several
+implementations.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * In memory JVM lock.
+ */
+public class JvmLock implements Lock {
+
+ /**
+ * Reentrant lock to use.
+ */
+ private ReentrantLock _lock;
+
+ /**
+ * In-memory lock.
+ */
+ public JvmLock() {
+ _lock = new ReentrantLock(true);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#acquire()
+ */
+ public void acquire() {
+ _lock.lock();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#release()
+ */
+ public void release() {
+ _lock.unlock();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+/**
+ * Represents a re-entrant lock.
+ * Implementations can provide inmemory JVM locking or full cluster safe locking
+ * mechanisms.
+ */
+public interface Lock {
+
+ /**
+ * Acquires the lock.
+ */
+ void acquire();
+
+ /**
+ * Releases the lock.
+ */
+ void release();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * Locking advice. This automatically synchronized an object using a given lock.
+ */
+public class LockAdvice implements MethodInterceptor {
+
+ /**
+ * Lock to use.
+ */
+ private Lock _lock;
+
+ /**
+ * Constructs lock advice.
+ * @param aLock Lock to use.
+ */
+ public LockAdvice(Lock aLock) {
+ _lock = aLock;
+ }
+
+ /* (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ public Object invoke(MethodInvocation aInvocation) throws Throwable {
+ _lock.acquire();
+ try {
+ return aInvocation.proceed();
+ } finally {
+ _lock.release();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.HashSet;
+
+
+/**
+ * Read-write lock for allowing multiple concurrent readers or at most one
+ * writer. This implementation does not aim for high performance but for
+ * robustness and simplicity. Users of this class should not synchronize on
+ * objects of this class.
+ */
+public class ReadWriteLock {
+ /**
+ * Sets containing the references to the threads that are currently
+ * reading. This administration is useful to check that the lock has
+ * already been acquired before it is release. This check adds robustness
+ * to the application.
+ */
+ private HashSet<Thread> _readers;
+
+ /**
+ * The thread that has acquired the lock for writing or null if no such
+ * thread exists currently.
+ */
+ private Thread _writer;
+
+ /**
+ * Constructs read-write lock.
+ */
+ public ReadWriteLock() {
+ _readers = new HashSet<Thread>();
+ _writer = null;
+ }
+
+ /**
+ * Acquires the lock for reading. This call will block until the lock can
+ * be acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireRead() {
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Read lock already acquired by current thread: "
+ + Thread.currentThread());
+ }
+
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire the read lock while already holding a write lock: "
+ + Thread.currentThread());
+ }
+
+ while (_writer != null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _readers.add(Thread.currentThread());
+ }
+
+ /**
+ * Releases the lock for reading. Note: This implementation assumes that
+ * the lock has already been acquired for reading previously.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired by
+ * this thread.
+ */
+ public synchronized void releaseRead() {
+ if (!_readers.remove(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Cannot release read lock because current thread has not acquired it.");
+ }
+
+ if (_readers.size() == 0) {
+ notifyAll();
+ }
+ }
+
+ /**
+ * Acquires the lock for writing. This call will block until the lock has
+ * been acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireWrite() {
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the write lock: "
+ + Thread.currentThread());
+ }
+
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the read lock: "
+ + Thread.currentThread());
+ }
+
+ // wait until there are no more writers and no more
+ // readers
+ while ((_writer != null) || (_readers.size() > 0)) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _writer = Thread.currentThread();
+
+ // notification not necessary since all writers and
+ // readers are now blocked by this thread.
+ }
+
+ /**
+ * Releases the lock for writing.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired.
+ */
+ public synchronized void releaseWrite() {
+ if (_writer != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Cannot release write lock because it was not acquired. ");
+ }
+
+ _writer = null;
+ notifyAll();
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides utilities for dealing with concurrency.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical and of different boolean conditions.
+ */
+public class AndCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public AndCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the and condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical and.
+ */
+ public AndCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (!condition.matches(aObject)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+
+/**
+ * Determines if an object matches a certain condition.
+ */
+public interface Condition<T> {
+
+ /**
+ * Determines if an object matches a condition.
+ * @param aObject object to match.
+ * @return True iff the object matches.
+ */
+ boolean matches(T aObject);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Condition which always returns a fixed value.
+ */
+public class FixedCondition<T> implements Condition<T> {
+
+ private boolean _value;
+
+ /**
+ * Constructs the condition.
+ * @param aValue Fixed value of the condition.
+ */
+ public FixedCondition(boolean aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical or of different boolean conditions.
+ */
+public class OrCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public OrCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the or condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical or.
+ */
+ public OrCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (condition.matches(aObject)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+/**
+ * Condition to check whether a given property value matches a certain
+ * regular expression.
+ */
+public class PropertyRegexCondition<T> implements Condition<T> {
+
+ /**
+ * Property name.
+ */
+ private String _property;
+
+ /**
+ * Regular expression.
+ */
+ private Pattern _regex;
+
+ /**
+ * Whether or not to convert the value to lowercase before matching.
+ */
+ private boolean _tolower;
+
+ /**
+ * Constructs the condition.
+ * @param aProperty Name of the property to examine.
+ * @param aRegex Regular expression to use.
+ * @param aTolower Whether or not to convert the value to lowercase before matching.
+ */
+ public PropertyRegexCondition(String aProperty, String aRegex, boolean aTolower) {
+ _property = aProperty;
+ _regex = Pattern.compile(aRegex);
+ _tolower = aTolower;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ try {
+ String value = PropertyUtils.getProperty(aObject, _property) + "";
+ if ( _tolower ) {
+ value = value.toLowerCase();
+ }
+ Matcher matcher = _regex.matcher(value);
+ return matcher.matches();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides some basic support classes for checking boolean conditions
+on objects.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Bean factory used to obtain objects in a transparent way.
+ */
+public interface BeanFactory {
+
+ /**
+ * Finds a bean based on id.
+ * @param aId Id of the bean.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ Object find(String aId);
+
+ /**
+ * Finds a bean of the given class and which can be cast to the
+ * specified class. This is typically used by specifying the interface
+ * class for retrieving an implementation of that class. This
+ * means that the bean implementing the class is configured in the bean factory
+ * with id equal to the class name of the interface.
+ * @param aClass Class of the object to find.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(Class<T> aClass);
+
+ /**
+ * Finds a bean with the given id which can be cast to the specified
+ * class.
+ * @param <T> Type of the object to get.
+ * @param aId Id of the object to lookup.
+ * @param aClass Class that the object must extends.
+ * @return Object, always non-null.
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(String aId, Class<T> aClass);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Exception thrown by the BeanFactory if an object could not be found.
+ */
+public class BeanFactoryException extends RuntimeException {
+ static final long serialVersionUID = -1215992188624874902L;
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public BeanFactoryException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aThrowable Cause of the exception.
+ */
+ public BeanFactoryException(String aMsg, Throwable aThrowable) {
+ super(aMsg, aThrowable);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * The standard means to obtain the bean factory. This works by reading a
+ * property {@value #BEAN_FACTORY_CLASS} from a property file named
+ * {@value #BEAN_KERNEL_PROP_FILE} from the class path. This property identifies
+ * the bean factory implementation to use. The configured bean factory must have
+ * a no-arg constructor.
+ */
+public final class BeanKernel {
+
+ private static final Log LOG = LogFactory.getLog(BeanKernel.class);
+
+ /**
+ * Bean factory kernel properties file.
+ */
+ private static final String BEAN_KERNEL_PROP_FILE = "org.wamblee.beanfactory.properties";
+
+ /**
+ * Name of the property to define the name of the bean factory class to use.
+ * THis class must have a public default constructor.
+ */
+ private static final String BEAN_FACTORY_CLASS = "org.wamblee.beanfactory.class";
+
+ /**
+ * Cached bean factory.
+ */
+ private static BeanFactory BEAN_FACTORY;
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private BeanKernel() {
+ // Empty
+ }
+
+ /**
+ * Overrides the default mechanism for looking up the bean factory by
+ * specifying it yourself.
+ *
+ * @param aOverride
+ * Override bean factory.
+ */
+ public static void overrideBeanFactory(BeanFactory aOverride) {
+ BEAN_FACTORY = aOverride;
+ }
+
+ /**
+ * Gets the bean factory.
+ *
+ * @return Bean factory.
+ */
+ public static BeanFactory getBeanFactory() {
+ synchronized (BeanFactory.class) {
+ if (BEAN_FACTORY == null) {
+ BEAN_FACTORY = lookupBeanFactory(BEAN_KERNEL_PROP_FILE);
+ }
+ }
+ return BEAN_FACTORY;
+ }
+
+ /**
+ * Lookup the bean factory based on the properties file.
+ *
+ * @return Bean factory.
+ */
+ static BeanFactory lookupBeanFactory(String aPropertyFilename) {
+ InputResource resource = new ClassPathResource(aPropertyFilename);
+ InputStream is;
+ try {
+ is = resource.getInputStream();
+ } catch (IOException e) {
+ throw new BeanFactoryException("Cannot open resource " + resource,
+ e);
+ }
+ try {
+ Properties props = new Properties();
+ props.load(is);
+ String className = props.getProperty(BEAN_FACTORY_CLASS);
+ Class beanFactory = Class.forName(className);
+ return (BeanFactory) beanFactory.newInstance();
+ } catch (Exception e) {
+ throw new BeanFactoryException("Cannot read from resource "
+ + resource, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // last resort cannot do much now.
+ LOG.error("Error closing resource " + resource);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import java.io.IOException;
+import java.io.File;
+import java.net.URLClassLoader;
+import java.net.URL;
+import java.lang.reflect.Method;
+
+/**
+ * Utility for working with the class loader. Based on the ClassPathHacker
+ * example found on the internet.
+ */
+public class ClassLoaderUtils {
+
+ // No logging in this class to keep the required class libraries
+ // limited to the standard java classes. This allows use of the
+ // utilities in an environment with a very limited classpath.
+
+ private static final String JAR_SUFFIX = ".jar";
+
+ /**
+ * Adds all jars in the given directory to the class path.
+ * @param aDirectory Directory.
+ * @throws IOException
+ */
+ public static void addJarsInDirectory(File aDirectory) throws IOException {
+ System.out.println("directory '" + aDirectory + "'");
+
+ for (File aFile : aDirectory.listFiles()) {
+ System.out
+ .println("Considering '" + aFile.getCanonicalPath() + "'");
+ if (aFile.getName().toLowerCase().endsWith(JAR_SUFFIX)) {
+ System.out.println("Adding '" + aFile.getCanonicalPath()
+ + "' to classpath.");
+ addFile(aFile);
+ }
+ }
+ }
+
+ /**
+ * Adds a file to the classpath.
+ * @param aFilename Filename to add.
+ * @throws IOException
+ */
+ public static void addFile(String aFilename) throws IOException {
+ File f = new File(aFilename);
+ addFile(f);
+ }
+
+ /**
+ * Adds a file to the classpath.
+ * @param aFile File to add.
+ * @throws IOException
+ */
+ public static void addFile(File aFile) throws IOException {
+ addURL(aFile.toURL());
+ }
+
+ /**
+ * Adds a url to the classpath.
+ * @param aUrl Url to add.
+ * @throws IOException
+ */
+ public static void addURL(URL aUrl) throws IOException {
+
+ URLClassLoader sysloader = (URLClassLoader) ClassLoader
+ .getSystemClassLoader();
+ Class sysclass = URLClassLoader.class;
+
+ try {
+ Method method = sysclass.getDeclaredMethod("addURL", new Class[]{ URL.class } );
+ method.setAccessible(true);
+ method.invoke(sysloader, new Object[] { aUrl });
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw new IOException(
+ "Error, could not add URL to system classloader");
+ }
+
+ }
+
+}
--- /dev/null
+
+
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Represents a pair of objects. This is inspired on the C++ Standard Template
+ * Library pair template.
+ *
+ * @param <T>
+ * Type of the first object.
+ * @param <U>
+ * Type of the second object.
+ */
+public class Pair<T, U> {
+
+ private T _t;
+
+ private U _u;
+
+ /**
+ * Constructs the pair.
+ *
+ * @param aT
+ * First object.
+ * @param aU
+ * Second object.
+ */
+ public Pair(T aT, U aU) {
+ _t = aT;
+ _u = aU;
+ }
+
+ /**
+ * Copies a pair.
+ *
+ * @param aPair
+ * Pair to copy.
+ */
+ public Pair(Pair<T, U> aPair) {
+ _t = aPair._t;
+ _u = aPair._u;
+ }
+
+ /**
+ * Gets the first object of the pair.
+ *
+ * @return First object.
+ */
+ public T getFirst() {
+ return _t;
+ }
+
+ /**
+ * Gets the second object of the pair.
+ *
+ * @return Second object.
+ */
+ public U getSecond() {
+ return _u;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.access.BeanFactoryLocator;
+import org.springframework.beans.factory.access.BeanFactoryReference;
+import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
+
+/**
+ * Bean factory which uses Spring. This bean factory cannot be configured
+ * directly in the {@link org.wamblee.general.BeanKernel} because it does not
+ * provide a default no-arg constructor. Therefore, it must be delegated to or
+ * it must tbe subclassed to provide a default constructor.
+ */
+public class SpringBeanFactory implements BeanFactory {
+
+ private BeanFactoryReference _factoryReference;
+
+ /**
+ * Constructs the bean factory.
+ *
+ * @param aSelector
+ * Selector to find the appropriate bean ref context.
+ * @param aFactoryName
+ * Spring bean factory to use.
+ */
+ public SpringBeanFactory(String aSelector, String aFactoryName) {
+ try {
+ BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator
+ .getInstance(aSelector);
+ _factoryReference = locator.useBeanFactory(aFactoryName);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(
+ "Could not load bean factory: selector = '" + aSelector
+ + "', factory = '" + aFactoryName + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String,
+ * java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _factoryReference.getFactory().getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Gets the spring bean factory.
+ * @return Spring bean factory.
+ */
+ public org.springframework.beans.factory.BeanFactory getSpringBeanFactory() {
+ return _factoryReference.getFactory();
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several general purpose support classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents an input resource in the classpath.
+ */
+public class ClassPathResource implements InputResource {
+ /**
+ * Resource name.
+ */
+ private String _resource;
+
+ /**
+ * Construct the class path resource.
+ *
+ * @param aResource
+ * Resource
+ */
+ public ClassPathResource(String aResource) {
+ _resource = aResource;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream stream = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(_resource);
+ if (stream == null) {
+ throw new IOException("Class path resource '" + _resource
+ + "' not found.");
+ }
+ return stream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "ClassPathResource(" + _resource + ")";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Monitors a directory for changes.
+ */
+public class DirectoryMonitor {
+
+ private static final Log LOG = LogFactory.getLog(DirectoryMonitor.class);
+
+ public static interface Listener {
+ void fileChanged(File aFile);
+ void fileCreated(File aFile);
+ void fileDeleted(File aFile);
+ };
+
+ private File _directory;
+ private FileFilter _filter;
+ private Listener _listener;
+ private Map<File,Date> _contents;
+
+ public DirectoryMonitor(File aDirectory, FileFilter aFilefilter, Listener aListener) {
+ _directory = aDirectory;
+ if ( !_directory.isDirectory()) {
+ throw new IllegalArgumentException("Directory '" + _directory + "' does not exist");
+ }
+ _filter = aFilefilter;
+ _listener = aListener;
+ _contents = new HashMap<File,Date>();
+ }
+
+ public void poll() {
+ LOG.debug("Polling " + _directory);
+ Map<File,Date> newContents = new HashMap<File,Date>();
+ File[] files = _directory.listFiles(_filter);
+ for (File file: files) {
+ if ( _contents.containsKey(file)) {
+ Date oldDate = _contents.get(file);
+ if (file.lastModified() != oldDate.getTime()) {
+ _listener.fileChanged(file);
+ } else {
+ // No change.
+ }
+ _contents.remove(file);
+ newContents.put(file, new Date(file.lastModified()));
+ } else {
+ _listener.fileCreated(file);
+ newContents.put(file, new Date(file.lastModified()));
+ }
+ }
+ for (File file: _contents.keySet()) {
+ _listener.fileDeleted(file);
+ }
+ _contents = newContents;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Resource implemention for reading from a file.
+ */
+public class FileResource implements InputResource {
+ /**
+ * File to read.
+ */
+ private File _file;
+
+ /**
+ * Constructs the resource.
+ *
+ * @param aFile
+ * File to read.
+ */
+ public FileResource(File aFile) {
+ _file = aFile;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(_file));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a resource from which information can be read.
+ */
+public interface InputResource {
+ /**
+ * Gets the input stream to the resource. The obtained input stream must be
+ * closed once reading has finished.
+ *
+ * @return Input stream to the resource, never null.
+ * @throws IOException
+ * in case the resource cannot be found.
+ */
+ InputStream getInputStream() throws IOException;
+}
--- /dev/null
+package org.wamblee.io;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class SimpleProcess {
+
+ private static final Log LOG = LogFactory.getLog(SimpleProcess.class);
+
+ private File _directory;
+
+ private String[] _cmd;
+
+ private String _stdout;
+ private String _stderr;
+
+ public SimpleProcess(File aDirectory, String[] aCmd) {
+ _directory = aDirectory;
+ _cmd = aCmd;
+ }
+
+ /**
+ * @return the stdout
+ */
+ public String getStdout() {
+ return _stdout;
+ }
+
+ /**
+ * @return the stderr
+ */
+ public String getStderr() {
+ return _stderr;
+ }
+
+ /**
+ * Runs the process and blocks until it is done.
+ *
+ * @return Exit status of the process.
+ * @throws IOException
+ * In case of problems.
+ */
+ public int run() throws IOException {
+ return runImpl();
+ }
+
+ private int runImpl() throws IOException {
+ try {
+ String fullcmd = "";
+ for (String part: _cmd) {
+ fullcmd += " " + part;
+ }
+ LOG.debug("Executing '" + fullcmd + "' in directory '" + _directory
+ + "'");
+ java.lang.Process proc = Runtime.getRuntime().exec(_cmd, null, _directory);
+
+ // Read standard output and error in separate threads to avoid
+ // deadlock.
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+ Thread stdoutReader = readAndLogStream("STDOUT> ", proc
+ .getInputStream(), stdout);
+ Thread stderrReader = readAndLogStream("STDERR> ", proc
+ .getErrorStream(), stderr);
+
+ try {
+ proc.waitFor();
+ } catch (InterruptedException e) {
+ IOException exception = new IOException(
+ "Process was terminated: " + this);
+ exception.initCause(e);
+ throw exception;
+ }
+ waitForReader(stdoutReader);
+ waitForReader(stderrReader);
+
+ _stdout = stdout.toString();
+ _stderr = stderr.toString();
+
+ if (proc.exitValue() != 0) {
+ LOG.warn("Exit value was non-zero: " + this);
+ } else {
+ LOG.debug("Process finished");
+ }
+ return proc.exitValue();
+ } catch (IOException e) {
+ IOException exception = new IOException("Error executing process: "
+ + this);
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ private void waitForReader(Thread aReaderThread) {
+ try {
+ aReaderThread.join();
+ } catch (InterruptedException e) {
+ LOG
+ .warn(this
+ + ": error waiting for output stream reader of process to finish");
+ }
+ }
+
+ private Thread readAndLogStream(final String aPrefix,
+ final InputStream aStream, final Writer aOutput) {
+ Thread inputReader = new Thread() {
+ @Override
+ public void run() {
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new InputStreamReader(aStream));
+ String str;
+ while ((str = br.readLine()) != null) {
+ LOG.debug(aPrefix + str);
+ aOutput.write(str);
+ }
+ } catch (IOException e) {
+ LOG.warn(SimpleProcess.this + ": error reading input stream", e);
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException e) {
+ LOG.warn("Error closing stream " + aPrefix);
+ }
+ }
+ }
+ }
+ };
+ inputReader.start();
+ return inputReader;
+ }
+
+ @Override
+ public String toString() {
+ String fullcmd = "";
+ for (String part: _cmd) {
+ fullcmd += part + " ";
+ }
+ return "process(dir = '" + _directory + "', cmd = '" + fullcmd + "')";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input resource based on an input stream.
+ */
+public class StreamResource implements InputResource {
+ /**
+ * Input stream to read.
+ */
+ private InputStream _stream;
+
+ /**
+ * Constructs a resource.
+ *
+ * @param aStream
+ * Input stream to read.
+ */
+ public StreamResource(InputStream aStream) {
+ _stream = aStream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return _stream;
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several support utilities for IO related functionality.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Default observer notifier which calls
+ * {@link org.wamblee.observer.Observer#send(ObservableType, Event)}
+ * immediately.
+ */
+public class DefaultObserverNotifier<ObservableType, Event> implements
+ ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Constructs the notifier.
+ *
+ */
+ public DefaultObserverNotifier() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.observer.ObserverNotifier#update(org.wamblee.observer.Observer,
+ * ObservableType, Event)
+ */
+ public void update(Observer<ObservableType, Event> aObserver,
+ ObservableType aObservable, Event aEvent) {
+ aObserver.send(aObservable, aEvent);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements subscription and notification logic for an observer pattern. This
+ * class is thread safe.
+ */
+public class Observable<ObservableType, Event> {
+
+ private static final Logger LOGGER = Logger.getLogger(Observable.class);
+
+ /**
+ * Observable.
+ */
+ private ObservableType _observable;
+
+ /**
+ * Used to notify observers.
+ */
+ private ObserverNotifier<ObservableType, Event> _notifier;
+
+ /**
+ * Map of subscription to observer.
+ */
+ private Map<Long, Observer<ObservableType, Event>> _observers;
+
+ /**
+ * Counter for subscriptions. Holds the next subscription.
+ */
+ private long _counter;
+
+ /**
+ * Constructs the observable.
+ *
+ * @param aObservable
+ * Observable this instance is used for.
+ * @param aNotifier
+ * Object used for implementing notification of listeners.
+ */
+ public Observable(ObservableType aObservable,
+ ObserverNotifier<ObservableType, Event> aNotifier) {
+ _observable = aObservable;
+ _notifier = aNotifier;
+ _observers = new TreeMap<Long, Observer<ObservableType, Event>>();
+ _counter = 0;
+ }
+
+ /**
+ * Subscribe an obvers.
+ *
+ * @param aObserver
+ * Observer to subscribe.
+ * @return Event Event to send.
+ */
+ public synchronized long subscribe(Observer<ObservableType, Event> aObserver) {
+ long subscription = _counter++; // integer rage is so large it will
+ // never roll over.
+ _observers.put(subscription, aObserver);
+ return subscription;
+ }
+
+ /**
+ * Unsubscribe an observer.
+ *
+ * @param aSubscription
+ * Subscription which is used
+ * @throws IllegalArgumentException
+ * In case the subscription is not known.
+ */
+ public synchronized void unsubscribe(long aSubscription) {
+ Object obj = _observers.remove(aSubscription);
+ if (obj == null) {
+ throw new IllegalArgumentException("Subscription '" + aSubscription
+ + "'");
+ }
+ }
+
+ /**
+ * Gets the number of subscribed observers.
+ *
+ * @return Number of subscribed observers.
+ */
+ public int getObserverCount() {
+ return _observers.size();
+ }
+
+ /**
+ * Notifies all subscribed observers.
+ *
+ * @param aEvent
+ * Event to send.
+ */
+ public void send(Event aEvent) {
+ // Make sure we do the notification while not holding the lock to avoid
+ // potential deadlock
+ // situations.
+ List<Observer<ObservableType, Event>> observers = new ArrayList<Observer<ObservableType, Event>>();
+ synchronized (this) {
+ observers.addAll(_observers.values());
+ }
+ for (Observer<ObservableType, Event> observer : observers) {
+ _notifier.update(observer, _observable, aEvent);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ if (_observers.size() > 0) {
+ LOGGER
+ .error("Still observers registered at finalization of observer!");
+ for (Observer observer : _observers.values()) {
+ LOGGER.error(" observer: " + observer);
+ }
+ }
+
+ super.finalize();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * This is a type-safe version of {@link java.util.Observable}.
+ */
+public interface Observer<ObservableType, Event> {
+
+ /**
+ * Called when an event has occurred on the observable.
+ * @param aObservable Observable.
+ * @param aEvent Event.
+ */
+ void send(ObservableType aObservable, Event aEvent);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Implementation of notification of subscribers.
+ */
+public interface ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Notifies an observer.
+ *
+ * @param aObserver Observer to notify
+ * @param aObservable Observable at which the event occured.
+ * @param aEvent Event that occured.
+ */
+ void update(Observer<ObservableType, Event> aObserver, ObservableType aObservable, Event aEvent);
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for the observer pattern.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of Persistent.
+ */
+public abstract class AbstractPersistent implements Persistent {
+
+ /**
+ * Primary key.
+ */
+ private Serializable _primaryKey;
+
+ /**
+ * Version.
+ */
+ private int _version;
+
+ /**
+ * Constructs the object.
+ *
+ */
+ protected AbstractPersistent() {
+ _primaryKey = null;
+ _version = -1;
+ }
+
+ /**
+ * Copy constructor.
+ * @param aPersistent Object to copy.
+ */
+ protected AbstractPersistent(AbstractPersistent aPersistent) {
+ _primaryKey = aPersistent._primaryKey;
+ _version = aPersistent._version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPrimaryKey()
+ */
+ public Serializable getPrimaryKey() {
+ return _primaryKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPrimaryKey(java.io.Serializable)
+ */
+ public void setPrimaryKey(Serializable aKey) {
+ _primaryKey = aKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPersistedVersion()
+ */
+ public int getPersistedVersion() {
+ return _version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPersistedVersion(int)
+ */
+ public void setPersistedVersion(int aVersion) {
+ _version = aVersion;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Interface for persistent objects. This defines required functionality for all objects
+ * that are persisted.
+ *
+ * Objects that implement this interface and which implement
+ * {@link java.lang.Object#equals(java.lang.Object)}
+ * should exclude the primary key and version from determining equality.
+ */
+public interface Persistent {
+
+ /**
+ * Gets the primary key.
+ * @return Primary key.
+ * @see #setPrimaryKey(Serializable)
+ */
+ Serializable getPrimaryKey();
+
+ /**
+ * Sets the primary key.
+ * @param aKey Primary key.
+ * @see #getPrimaryKey()
+ */
+ void setPrimaryKey(Serializable aKey);
+
+ /**
+ * Gets the version.
+ * @return Version.
+ * @see #setPersistedVersion(int)
+ */
+ int getPersistedVersion();
+
+ /**
+ * Sets the version.
+ * @param aVersion Version.
+ * @see #getPersistedVersion()
+ */
+ void setPersistedVersion(int aVersion);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Hibernate mapping files to use.
+ */
+public class HibernateMappingFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of hibernate mapping files.
+ *
+ */
+ public HibernateMappingFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public HibernateMappingFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Extension of
+ * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
+ */
+public class HibernateSupport extends HibernateDaoSupport {
+
+ private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
+
+ /**
+ * This class provided an equality operation based on the object reference
+ * of the wrapped object. This is required because we cannto assume that the
+ * equals operation has any meaning for different types of persistent
+ * objects. This allows us to use the standard collection classes for
+ * detecting cyclic dependences and avoiding recursion.
+ *
+ */
+ private static final class ObjectElem {
+ private Object _object;
+
+ public ObjectElem(Object aObject) {
+ _object = aObject;
+ }
+
+ public boolean equals(Object aObj) {
+ return ((ObjectElem) aObj)._object == _object;
+ }
+
+ public int hashCode() {
+ return _object.hashCode();
+ }
+ }
+
+ /**
+ * Constructs the object.
+ *
+ */
+ public HibernateSupport() {
+ // Empty
+ }
+
+ /**
+ * Performes a hibernate <code>Session.merge()</code> and updates the
+ * object with the correct primary key and version. This is an extension to
+ * the hibernate merge operation because hibernate itself leaves the object
+ * passed to merge untouched.
+ *
+ * Use this method with extreme caution since it will recursively load all
+ * objects that the current object has relations with and for which
+ * cascade="merge" was specified in the Hibernate mapping file.
+ *
+ * @param aPersistent
+ * Object to merge.
+ */
+ public void merge(Persistent aPersistent) {
+ merge(getHibernateTemplate(), aPersistent);
+ }
+
+ /**
+ * As {@link #merge(Persistent)} but with a given template. This method can
+ * be accessed in a static way.
+ *
+ * @param aTemplate
+ * Hibernate template
+ * @param aPersistent
+ * Object to merge.
+ */
+ public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
+ Persistent merged = (Persistent) aTemplate.merge(aPersistent);
+ processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
+ }
+
+ /**
+ * Copies primary keys and version from the result of the merged to the
+ * object that was passed to the merge operation. It does this by traversing
+ * the properties of the object. It copies the primary key and version for
+ * objects that implement {@link Persistent} and applies the same rules to
+ * objects in maps and sets as well (i.e. recursively).
+ *
+ * @param aPersistent
+ * Object whose primary key and version are to be set.
+ * @param aMerged
+ * Object that was the result of the merge.
+ * @param aProcessed
+ * List of already processed Persistent objects of the persistent
+ * part.
+ */
+ public static void processPersistent(Persistent aPersistent,
+ Persistent aMerged, List<ObjectElem> aProcessed) {
+ if (aPersistent == null && aMerged == null) {
+ return;
+ }
+ if (aPersistent == null || aMerged == null) {
+ throw new RuntimeException("persistent or merged object is null '"
+ + aPersistent + "'" + " '" + aMerged + "'");
+ }
+ ObjectElem elem = new ObjectElem(aPersistent);
+ if (aProcessed.contains(elem)) {
+ return; // already processed.
+ }
+ aProcessed.add(elem);
+
+ LOG.info("Setting pk/version on " + aPersistent + " from " + aMerged);
+
+ if (aPersistent.getPrimaryKey() != null
+ && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
+ LOG.error("Mismatch between primary key values: " + aPersistent
+ + " " + aMerged);
+ } else {
+ aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
+ aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
+ }
+
+ Method[] methods = aPersistent.getClass().getMethods();
+ for (Method getter : methods) {
+ if (getter.getName().startsWith("get")) {
+ Class returnType = getter.getReturnType();
+
+ try {
+ if (Set.class.isAssignableFrom(returnType)) {
+ Set merged = (Set) getter.invoke(aMerged);
+ Set persistent = (Set) getter.invoke(aPersistent);
+ processSet(persistent, merged, aProcessed);
+ } else if (List.class.isAssignableFrom(returnType)) {
+ List merged = (List) getter.invoke(aMerged);
+ List persistent = (List) getter.invoke(aPersistent);
+ processList(persistent, merged, aProcessed);
+ } else if (Map.class.isAssignableFrom(returnType)) {
+ Map merged = (Map) getter.invoke(aMerged);
+ Map persistent = (Map) getter.invoke(aPersistent);
+ processMap(persistent, merged, aProcessed);
+ } else if (Persistent.class.isAssignableFrom(returnType)) {
+ Persistent merged = (Persistent) getter.invoke(aMerged);
+ Persistent persistent = (Persistent) getter
+ .invoke(aPersistent);
+ processPersistent(persistent, merged, aProcessed);
+ } else if (returnType.isArray()
+ && Persistent.class.isAssignableFrom(returnType
+ .getComponentType())) {
+ Persistent[] merged = (Persistent[]) getter
+ .invoke(aMerged);
+ Persistent[] persistent = (Persistent[]) getter
+ .invoke(aPersistent);
+ for (int i = 0; i < persistent.length; i++) {
+ processPersistent(persistent[i], merged[i],
+ aProcessed);
+ }
+ }
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Process the persistent objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processList(List aPersistent, List aMerged,
+ List<ObjectElem> aProcessed) {
+ Object[] merged = aMerged.toArray();
+ Object[] persistent = aPersistent.toArray();
+ if (merged.length != persistent.length) {
+ throw new RuntimeException("Array sizes differ " + merged.length
+ + " " + persistent.length);
+ }
+ for (int i = 0; i < merged.length; i++) {
+ assert merged[i].equals(persistent[i]);
+ if (merged[i] instanceof Persistent) {
+ processPersistent((Persistent) persistent[i],
+ (Persistent) merged[i], aProcessed);
+ }
+ }
+ }
+
+ /**
+ * Process the persistent objects in sets.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processSet(Set aPersistent, Set aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Array sizes differ " + aMerged.size()
+ + " " + aPersistent.size());
+ }
+ for (Object merged : aMerged) {
+ // Find the object that equals the merged[i]
+ for (Object persistent : aPersistent) {
+ if (persistent.equals(merged)) {
+ processPersistent((Persistent) persistent,
+ (Persistent) merged, aProcessed);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the Map objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processMap(Map aPersistent, Map aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Sizes differ " + aMerged.size() + " "
+ + aPersistent.size());
+ }
+ Set keys = aMerged.keySet();
+ for (Object key : keys) {
+ if (!aPersistent.containsKey(key)) {
+ throw new RuntimeException("Key '" + key + "' not found");
+ }
+ Object mergedValue = aMerged.get(key);
+ Object persistentValue = aPersistent.get(key);
+ if (mergedValue instanceof Persistent) {
+ if (persistentValue instanceof Persistent) {
+ processPersistent((Persistent) persistentValue,
+ (Persistent) mergedValue, aProcessed);
+ } else {
+ throw new RuntimeException(
+ "Value in original object is null, whereas merged object contains a value");
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for persistence with hibernate.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for persistence.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * URI resolver that resolves stylesheets through the classpath.
+ */
+public class ClasspathUriResolver implements URIResolver {
+
+ /**
+ * Constructs the resolver.
+ *
+ */
+ public ClasspathUriResolver() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.xml.transform.URIResolver#resolve(java.lang.String,
+ * java.lang.String)
+ */
+ public Source resolve(String aHref, String aBase)
+ throws TransformerException {
+ InputResource xslt = new ClassPathResource(aHref);
+ try {
+ return new StreamSource(xslt.getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(
+ "Could not get XSLT style sheet in classpath '" + aHref
+ + "'", e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.dom4j.DocumentException;
+import org.dom4j.io.DOMReader;
+import org.dom4j.io.DOMWriter;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Some basic XML utilities for common reoccuring tasks for DOM documents.
+ */
+public final class DomUtils {
+
+ private static final Log LOG = LogFactory.getLog(DomUtils.class);
+
+ /**
+ * Disabled default constructor.
+ *
+ */
+ private DomUtils() {
+ // Empty.
+ }
+
+ /**
+ * Parses an XML document from a string.
+ *
+ * @param aDocument
+ * document.
+ * @return
+ */
+ public static Document read(String aDocument) throws XMLException {
+ ByteArrayInputStream is = new ByteArrayInputStream(aDocument.getBytes());
+ return read(is);
+ }
+
+ /**
+ * Parses an XML document from a stream.
+ *
+ * @param aIs
+ * Input stream.
+ * @return
+ */
+ public static Document read(InputStream aIs) throws XMLException {
+ try {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ return builder.parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+ }
+
+ /**
+ * Reads and validates a document against a schema.
+ *
+ * @param aIs
+ * Input stream.
+ * @param aSchema
+ * Schema.
+ * @return Parsed and validated document.
+ */
+ public static Document readAndValidate(InputStream aIs, InputStream aSchema)
+ throws XMLException {
+
+ try {
+ final Schema schema = SchemaFactory.newInstance(
+ XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
+ new StreamSource(aSchema));
+
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(true);
+ factory.setNamespaceAware(true);
+ factory.setSchema(schema);
+
+ return factory.newDocumentBuilder().parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aSchema.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing schema", e);
+ }
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+
+ }
+
+ /**
+ * Serializes an XML document to a stream.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @param aOs
+ * Output stream.
+ */
+ public static void serialize(Document aDocument, OutputStream aOs)
+ throws IOException {
+ XMLSerializer serializer = new XMLSerializer(aOs, new OutputFormat());
+ serializer.serialize(aDocument);
+ }
+
+ /**
+ * Serializes an XML document.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @return Serialized document.
+ */
+ public static String serialize(Document aDocument) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ serialize(aDocument, os);
+ return os.toString();
+ }
+
+ /**
+ * Converts a dom4j document into a w3c DOM document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return W3C DOM document.
+ */
+ public static Document convert(org.dom4j.Document aDocument)
+ throws DocumentException {
+ return new DOMWriter().write(aDocument);
+ }
+
+ /**
+ * Converts a W3C DOM document into a dom4j document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return Dom4j document.
+ */
+ public static org.dom4j.Document convert(Document aDocument) {
+ return new DOMReader().read(aDocument);
+ }
+
+ /**
+ * Removes duplicate attributes from a DOM tree.This is useful for
+ * postprocessing the output of JTidy as a workaround for a bug in JTidy.
+ *
+ * @param aNode
+ * Node to remove duplicate attributes from (recursively).
+ * Attributes of the node itself are not dealt with. Only the
+ * child nodes are dealt with.
+ */
+ public static void removeDuplicateAttributes(Node aNode) {
+ NodeList list = aNode.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element) {
+ removeDuplicateAttributes((Element) node);
+ removeDuplicateAttributes(node);
+ }
+ }
+ }
+
+ /**
+ * Removes duplicate attributes from an element.
+ *
+ * @param aElement
+ * Element.
+ */
+ private static void removeDuplicateAttributes(Element aElement) {
+ NamedNodeMap attributes = aElement.getAttributes();
+ Map<String, Attr> uniqueAttributes = new TreeMap<String, Attr>();
+ List<Attr> attlist = new ArrayList<Attr>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ if (uniqueAttributes.containsKey(attribute.getNodeName())) {
+ LOG.info("Detected duplicate attribute (will be removed)'"
+ + attribute.getNodeName() + "'");
+ }
+ uniqueAttributes.put(attribute.getNodeName(), attribute);
+ attlist.add(attribute);
+ }
+ // Remove all attributes from the element.
+ for (Attr att : attlist) {
+ aElement.removeAttributeNode(att);
+ }
+ // Add the unique attributes back to the element.
+ for (Attr att : uniqueAttributes.values()) {
+ aElement.setAttributeNode(att);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+/**
+ * Exception thrown in case of XML parsing problems.
+ */
+public class XMLException extends Exception {
+
+ public XMLException(String aMsg) {
+ super(aMsg);
+ }
+
+ public XMLException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Document;
+import org.wamblee.io.FileResource;
+
+/**
+ * XSL transformer for simplified usage of XSL transformations.
+ */
+public class XslTransformer {
+
+ private TransformerFactory _factory;
+
+ /**
+ * Constructs the URL resolver.
+ *
+ * @param aResolver
+ * URI resolver to use.
+ */
+ public XslTransformer(URIResolver aResolver) {
+ _factory = TransformerFactory.newInstance();
+ _factory.setURIResolver(aResolver);
+ }
+
+ /**
+ * Constructs the XSLT processor.
+ *
+ */
+ public XslTransformer() {
+ _factory = TransformerFactory.newInstance();
+ }
+
+ /**
+ * Resolves an XSLT based on URI.
+ * @param aXslt XSLT to resolve,
+ * @return Source for the XSLT
+ * @throws TransformerException In case the XSLT cannot be found.
+ */
+ public Source resolve(String aXslt) throws TransformerException {
+ URIResolver resolver = _factory.getURIResolver();
+ if (resolver == null) {
+ if (new File(aXslt).canRead()) {
+ try {
+ return new StreamSource(new FileResource(new File(aXslt))
+ .getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(e.getMessage(), e);
+ }
+ } else {
+ throw new TransformerException("Cannot read '" + aXslt + "'");
+ }
+ }
+ return resolver.resolve(aXslt, "");
+ }
+
+ /**
+ * Transforms a DOM document into another DOM document using a given XSLT
+ * transformation.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(Document aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new DOMSource(aDocument);
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document to a text output. This supports XSLT
+ * transformations that result in text documents.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSL transformation.
+ * @return Transformed document.
+ */
+ public String textTransform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamResult result = new StreamResult(os);
+ transform(source, result, aXslt);
+ return new String(os.toByteArray());
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aSource
+ * Document to transform.
+ * @param aResult
+ * Result of the transformation.
+ * @param aXslt
+ * XSLT to use.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public void transform(Source aSource, Result aResult, Source aXslt)
+ throws IOException, TransformerException {
+ try {
+ Transformer transformer = _factory.newTransformer(aXslt);
+ transformer.transform(aSource, aResult);
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException(
+ "Configuration problem of XSLT transformation", e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for XML processing.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=INFO
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+log4j.logger.org.wamblee.crawler=DEBUG
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import net.sf.ehcache.CacheException;
+
+import org.wamblee.io.TestResource;
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Cached object test.
+ */
+public class CachedObjectTest extends TestCase {
+
+ /**
+ *
+ */
+ private static final String EHCACHE_CONFIG = "ehcache.xml";
+
+ private static final int OBJECT_KEY = 10;
+
+ private CachedObject.Computation<Integer,Integer> _computation;
+ private int _ncomputations;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _computation = new CachedObject.Computation<Integer,Integer>() {
+ public Integer getObject(Integer aObjectKey) {
+ _ncomputations++;
+ return compute(aObjectKey);
+ };
+ };
+ _ncomputations = 0;
+ }
+
+ private int compute(int aValue) {
+ return aValue + 10;
+ }
+
+ private CachedObject<Integer, Integer> createCached(Cache<Integer,Integer> aCache) {
+ return new CachedObject<Integer, Integer>(aCache, OBJECT_KEY, _computation);
+ }
+
+ /**
+ * Verifies that upon first use, the cached object uses the computation to
+ * retrieve the object.
+ *
+ */
+ public void testComputation() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+
+ public void testInvalidateCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+ }
+
+ public void testBehaviorEhCache() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "test");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // explicit invalidation.
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(3, _ncomputations);
+
+ }
+
+ public void testBehaviorEhCacheDefault() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "undefined");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ }
+
+
+ public void testBehaviorForeverCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+ }
+
+ public void testBehaviorZeroCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(ncomp, _ncomputations);
+ }
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(101, _ncomputations);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.EventTracker;
+import org.wamblee.test.TimingUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the JVMLock.
+ */
+public abstract class AbstractLockTestCase extends TestCase {
+
+ protected static final int SLEEP_TIME = 1000;
+
+ protected static final String STARTED = "started";
+
+ protected static final String ACQUIRED = "acquired";
+
+ protected static final String RELEASED = "released";
+
+ private EventTracker<String> _tracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _tracker = new EventTracker<String>();
+ }
+
+ protected EventTracker<String> getTracker() {
+ return _tracker;
+ }
+
+ /**
+ * Must be implemented to generate the events
+ * {@link #STARTED}, {@link #ACQUIRED}, and {@link #RELEASED} in
+ * that order. The lock should be acquired for
+ * the time specified by {@link #SLEEP_TIME}.
+ * @return Thread which does the work.
+ */
+ protected abstract Thread runThread();
+
+ /**
+ * Tests the operation of the lock.
+ */
+ public void testLock() throws InterruptedException {
+ Thread t1 = runThread();
+ Thread t2 = runThread();
+ TimingUtils.sleep(SLEEP_TIME / 10); // give threads a chance to start
+ // up.
+ assertEquals(2, _tracker.getEventCount(STARTED)); // both threads
+ // should have
+ // started.
+ assertEquals(1, _tracker.getEventCount(ACQUIRED)); // one thread has
+ // acquired the
+ // lock.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(ACQUIRED)); // now the other
+ // thread could also
+ // acquire the lock
+ assertEquals(1, _tracker.getEventCount(RELEASED)); // and the first
+ // thread has
+ // released it.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(RELEASED)); // both threads
+ // should be
+ // finished.
+ t1.join();
+ t2.join();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Tests for the JVMLock.
+ */
+public class JvmLockTest extends AbstractLockTestCase {
+
+ private JvmLock _lock;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new JvmLock();
+ }
+
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ getTracker().eventOccurred(STARTED);
+ _lock.acquire();
+ getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ _lock.release();
+ getTracker().eventOccurred(RELEASED);
+ };
+ });
+ t.start();
+ return t;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.springframework.aop.framework.ProxyFactoryBean;
+import org.wamblee.test.TimingUtils;
+
+/**
+ *
+ */
+public class LockAdviceTest extends AbstractLockTestCase {
+
+ private class Runner implements Runnable {
+ public void run() {
+ LockAdviceTest.this.getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ }
+ }
+
+ private Runnable _target;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Runner runner = new Runner();
+ LockAdvice advice = new LockAdvice(new JvmLock());
+
+ ProxyFactoryBean support = new ProxyFactoryBean();
+ support.setInterfaces(new Class[]{ Runnable.class });
+ support.setTarget(runner);
+ support.addAdvice(advice);
+ _target = (Runnable)support.getObject();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#runThread()
+ */
+ @Override
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ getTracker().eventOccurred(STARTED);
+ _target.run();
+ getTracker().eventOccurred(RELEASED);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ };
+ });
+ t.start();
+ return t;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+
+/**
+ * Testing the read-write lock class. Note: in case of problems, test cases
+ * could hang.
+ *
+ * @see ReadWriteLock
+ */
+public class ReadWriteLockTest extends TestCase {
+ /**
+ *
+ */
+ private static final int HALF_SECOND = 500;
+ /**
+ *
+ */
+ private static final int ONE_SECOND = 1000;
+ /**
+ *
+ */
+ private static final int TWO_SECONDS = 2000;
+ private ReadWriteLock _lock;
+ private int _nReaders;
+ private int _nWriters;
+
+ /**
+ * Constructor for ReadWriteLockTest.
+ *
+ * @param aName
+ */
+ public ReadWriteLockTest(String aName) {
+ super(aName);
+ }
+
+ private synchronized int getReaderCount() {
+ return _nReaders;
+ }
+
+ private synchronized int getWriterCount() {
+ return _nWriters;
+ }
+
+ synchronized void incrementReaderCount() {
+ _nReaders++;
+ }
+
+ synchronized void incrementWriterCount() {
+ _nWriters++;
+ }
+
+ synchronized void decrementReaderCount() {
+ _nReaders--;
+ }
+
+ synchronized void decrementWriterCount() {
+ _nWriters--;
+ }
+
+ /*
+ * @see TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new ReadWriteLock();
+ }
+
+ /*
+ * @see TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ _lock = null;
+ super.tearDown();
+ }
+
+ /**
+ * Acquire and release a read lock.
+ */
+ public void testRead() {
+ _lock.acquireRead();
+ _lock.releaseRead();
+ }
+
+ /**
+ * Acquire and release a write lock.
+ */
+ public void testWrite() {
+ _lock.acquireWrite();
+ _lock.releaseWrite();
+ }
+
+ /**
+ * Verify concurrent access by multiple readers is possible.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleReaders() throws InterruptedException {
+ Runnable runnable = new ReadLocker(_lock, this, TWO_SECONDS);
+
+ Thread t1 = new Thread(runnable);
+ t1.start();
+
+ Thread t2 = new Thread(runnable);
+ t2.start();
+ Thread.sleep(ONE_SECOND);
+ assertTrue("Not enough readers!", getReaderCount() == 2);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that only one writer at a time can acquire the write lock.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testSingleWriter() throws InterruptedException {
+ WriteLocker writer = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer);
+ Thread t2 = new Thread(writer);
+
+ t1.start();
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue("Wrong writer count: " + getWriterCount(),
+ getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that multiple writers cannot acquire the write lock concurrently.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleWriters() throws InterruptedException {
+ WriteLocker writer1 = new WriteLocker(_lock, this, HALF_SECOND + ONE_SECOND);
+ WriteLocker writer2 = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer1);
+ Thread t2 = new Thread(writer2);
+
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1); // first writer still
+
+ // has the lock.
+ Thread.sleep(ONE_SECOND);
+
+ // at t = 2, the second writer still must have
+ // a lock.
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that after the first reader acquires a lock, a subsequent writer
+ * can only acquire the lock after the reader has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite1() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, reader still holding the
+ // lock so write lock cannot be acquired.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, read lock released and
+ // write lock must be acquired.
+ assertTrue("Wrong no. of readers: " + getReaderCount(),
+ getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that when multiple readers have acquired a read lock, the writer
+ * can only acquire the lock after all readers have released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite2() throws InterruptedException {
+ ReadLocker readLocker1 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ ReadLocker readLocker2 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ Thread t1 = new Thread(readLocker1);
+ Thread t2 = new Thread(readLocker2);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t3 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(ONE_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 2);
+ t3.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 2 seconds,
+ assertTrue(getReaderCount() == 2);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND);
+
+ // 3 seconds underway, first read lock must
+ // have been released.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(HALF_SECOND);
+
+ // 4 seconds underway, write lock must have
+ // been acquired.
+ assertTrue(getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+
+ t1.join();
+ t2.join();
+ t3.join();
+ }
+
+ /**
+ * Verify that after a writer acquires a lock, a subsequent reader can
+ * only acquire the lock after the writer has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite3() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t2.start(); // acquire write lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, writer still holding the
+ // lock so read lock cannot be acquired.
+ assertTrue(getWriterCount() == 1);
+ assertTrue(getReaderCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, write lock released and
+ // read lock must be acquired.
+ assertTrue("Wrong no. of writers: " + getReaderCount(),
+ getWriterCount() == 0);
+ assertTrue(getReaderCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /*
+ * The following test cases are for testing whether or not
+ * the read write lock checks the locking correctly.
+ * Strictly speaking, these checks wouldn't be necessary
+ * because it involves the contract of the ReadWriteLock which
+ * must be obeyed by users of the ReadWriteLock. Nevertheless,
+ * this is tested anyway to be absolutely sure.
+ */
+
+ /**
+ * Acquire a read lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseReadFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireRead();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseRead(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseWriteFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireWrite();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseWrite(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a read lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadTwice() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ // ok
+ return;
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a write lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteTwice() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for reading and directly afterwards acquire it for
+ * writing. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for writing and directly afterwards acquire it for
+ * reading. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a read lock and release it as a write lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByReleaseaWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.releaseWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock and release it as a read lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByReleaseRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.releaseRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+}
+
+
+/**
+ * ReadLocker acquires a read lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the read
+ * lock, and performs a callback after the lock has been released.
+ */
+class ReadLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireRead();
+ _lockTest.incrementReaderCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("ReadLocker thread was interrupted."
+ + Thread.currentThread());
+ }
+
+ _lock.releaseRead();
+ _lockTest.decrementReaderCount();
+ }
+}
+
+
+/**
+ * WriteLocker acquires a write lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the write
+ * lock, and performs a callback after the lock has been released.
+ */
+class WriteLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireWrite();
+ _lockTest.incrementWriterCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("WriteLocker thread was interrupted: "
+ + Thread.currentThread());
+ }
+
+ _lock.releaseWrite();
+ _lockTest.decrementWriterCount();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ */
+public class AndConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ AndCondition<Integer> and = new AndCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, and.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ AndCondition<Integer> and = new AndCondition<Integer>(conditions);
+ assertEquals(aResult, and.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, false);
+ checkResult(false, true, false);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, false);
+ checkResult(new boolean[]{ false, true, false }, false);
+ checkResult(new boolean[]{ false, false, true }, false);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ */
+public class GreaterThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public GreaterThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject > _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ */
+public class LessThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public LessThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject < _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ */
+public class OrConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ OrCondition<Integer> or = new OrCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, or.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ OrCondition<Integer> or = new OrCondition<Integer>(conditions);
+ assertEquals(aResult, or.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, true);
+ checkResult(false, true, true);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, true);
+ checkResult(new boolean[]{ false, true, false }, true);
+ checkResult(new boolean[]{ false, false, true }, true);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link org.wamblee.conditions.PropertyRegexCondition}.
+ */
+public class PropertyRegexConditionTest extends TestCase {
+
+ private boolean match(String aProperty, String aRegex, boolean aToLower, TestBean aBean) {
+ PropertyRegexCondition<TestBean> condition = new PropertyRegexCondition<TestBean>(aProperty, aRegex, aToLower );
+ return condition.matches(aBean);
+ }
+
+ private void checkMatch(String aProperty, String aRegex, boolean aToLower, TestBean aBean, boolean aResult) {
+ assertEquals( aResult, match(aProperty, aRegex, aToLower, aBean));
+ }
+
+ /**
+ * Verifies correct matching behavior for several cases.
+ *
+ */
+ public void testMatchProperty() {
+ TestBean bean = new TestBean("Hallo");
+ checkMatch("value", "Hallo", false, bean, true);
+ checkMatch("value", "all", false, bean, false);
+ checkMatch("value", ".a.*o", false, bean, true);
+ checkMatch("value", "hallo", false, bean, false); // no match when not converting to lower case.
+ checkMatch("value", "hallo", true, bean, true); // match!
+ }
+
+ /**
+ * Uses property regex condition for non-existing property.
+ * Verifies that a runtime exception is thrown.
+ *
+ */
+ public void testWrongProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("bla", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Applies condition to a private property. Verifies that a runtime
+ * exception is thrown.
+ *
+ */
+ public void testPrivateProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("privateValue", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ *
+ */
+public class TestBean {
+
+ private String _value;
+
+ public TestBean(String aValue) {
+ _value = aValue;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ private String getPrivateValue() {
+ return _value;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the bean kernel. The lookup of the bean factory itself can be tested
+ * only partially. Using a global property file for all test cases would tie all
+ * test cases together therefore no global property file is used.
+ */
+public class BeanKernelTest extends TestCase {
+
+ /**
+ * Loads the bean factory based on a property file configuration. Verifies
+ * the correct bean factory is loaded.
+ *
+ */
+ public void testLoadBeanFactoryFromProperties() {
+ BeanFactory factory = BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel.properties");
+ assertTrue(factory instanceof TestBeanFactory);
+ }
+
+ /**
+ * Loads the bean factory based on a non-existing property file.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentPropertyFile() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-nonexistent.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Loads the bean factory based on a property file with a non-existing
+ * bean factory defined in it.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentBeanFactory() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-wrong.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Retrieves a bean factory throug the bean kernel. Verifies that beans can
+ * be retrieved.
+ *
+ */
+ public void testRetrieveFactory() {
+ BeanKernel.overrideBeanFactory(new TestBeanFactory()); // bypass
+ // default
+ // property
+ // lookup
+ BeanFactory factory = BeanKernel.getBeanFactory();
+ assertNotNull(factory);
+ assertEquals("hello", factory.find(String.class));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the pair class.
+ */
+public class PairTest extends TestCase {
+
+ public void testPair() {
+ Pair<Integer, String> pair = new Pair<Integer, String>(10, "hello");
+ assertEquals(new Integer(10), pair.getFirst());
+ assertEquals("hello", pair.getSecond());
+
+ Pair<Integer, String> pair2 = new Pair<Integer, String>(pair);
+ assertEquals(new Integer(10), pair2.getFirst());
+ assertEquals("hello", pair2.getSecond());
+
+
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the spring bean factory.
+ */
+public class SpringBeanFactoryTest extends TestCase {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ public void testExistingBeanRefContext() {
+ SpringBeanFactory factory = new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "test");
+
+ String value1 = factory.find(String.class);
+ assertEquals("hello", value1);
+ String value2 = (String) factory.find("java.lang.String");
+ assertEquals("hello", value2);
+ String value3 = factory.find("java.lang.String", String.class);
+ assertEquals("hello", value3);
+
+ try {
+ factory.find("unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ public void testUnknownBeanFactory() {
+ try {
+ new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Test bean factory.
+ */
+public class TestBeanFactory extends SpringBeanFactory {
+
+
+ public TestBeanFactory() {
+ super("org/wamblee/general/beanRefContext.xml", "test");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the classpath resource.
+ */
+public class ClassPathResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource from the class path. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource.txt");
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource from the class path. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource-nonexistent.txt");
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+import org.apache.oro.io.AwkFilenameFilter;
+import org.apache.oro.io.RegexFilenameFilter;
+
+/**
+ *
+ */
+public class DirectoryMonitorTestProgram {
+
+ public static void main(String[] aArgs) throws Exception {
+
+ DirectoryMonitor monitor = new DirectoryMonitor(new File("."),
+ new AwkFilenameFilter(".*\\.txt"), new DirectoryMonitor.Listener() {
+ public void fileChanged(File aFile) {
+ System.out.println("changed " + aFile);
+ }
+ public void fileCreated(File aFile) {
+ System.out.println("created " + aFile);
+ }
+ public void fileDeleted(File aFile) {
+ System.out.println("deleted " + aFile);
+ }
+ });
+
+ for (;;) {
+ monitor.poll();
+ Thread.sleep(1000);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the file resource.
+ */
+public class FileResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource.txt"));
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource-nonexistent.txt"));
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.security.CodeSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * File system utilities.
+ */
+public final class FileSystemUtils {
+
+ private static final Log LOG = LogFactory.getLog(FileSystemUtils.class);
+
+ /**
+ * Test output directory relative to the sub project.
+ */
+ private static final String TEST_OUTPUT_DIR = "../target/testoutput";
+
+ /**
+ * Test input directory relative to the sub project.
+ */
+ private static final String TEST_INPUT_DIR = "../src/test/resources";
+
+ /*
+ * Disabled.
+ *
+ */
+ private FileSystemUtils() {
+ // Empty
+ }
+
+ /**
+ * Deletes a directory recursively. The test case will fail if the directory
+ * does not exist or if deletion fails.
+ *
+ * @param aDir
+ * Directory to delete.
+ */
+ public static void deleteDirRecursively(String aDir) {
+ deleteDirRecursively(new File(aDir));
+ }
+
+ /**
+ * Deletes a directory recursively. See {@link #deleteDirRecursively}.
+ *
+ * @param aDir
+ * Directory.
+ */
+ public static void deleteDirRecursively(File aDir) {
+ TestCase.assertTrue(aDir.isDirectory());
+
+ for (File file : aDir.listFiles()) {
+ if (file.isDirectory()) {
+ deleteDirRecursively(file);
+ } else {
+ delete(file);
+ }
+ }
+
+ delete(aDir);
+ }
+
+ /**
+ * Deletes a file or directory. The test case will fail if the file or
+ * directory does not exist or if deletion fails. Deletion of a non-empty
+ * directory will always fail.
+ *
+ * @param aFile
+ * File or directory to delete.
+ */
+ public static void delete(File aFile) {
+ TestCase.assertTrue(aFile.delete());
+ }
+
+ /**
+ * Gets a path relative to a sub project. This utility should be used to
+ * easily access file paths within a subproject without requiring any
+ * specific Eclipse configuration.
+ *
+ * @param aRelativePath
+ * Relative path.
+ * @param aTestClass
+ * Test class.
+ */
+ public static File getPath(String aRelativePath, Class aTestClass) {
+ CodeSource source = aTestClass.getProtectionDomain().getCodeSource();
+ if (source == null) {
+ LOG.warn("Could not obtain path for '" + aRelativePath
+ + "' for class " + aTestClass
+ + ", using relative path as is");
+ return new File(aRelativePath);
+ }
+ URL location = source.getLocation();
+ String protocol = location.getProtocol();
+ if (!protocol.equals("file")) {
+ LOG.warn("protocol is not 'file': " + location);
+ return new File(aRelativePath);
+ }
+
+ String path = location.getPath();
+ try {
+ path = URLDecoder.decode(location.getPath(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore it.. just don't decode
+ LOG.warn("Decoding path failed: '" + location.getPath() + "'", e );
+ }
+
+ return new File(new File(path).getParentFile(), aRelativePath);
+ }
+
+ /**
+ * Ensures that a directory hierarchy exists (recursively if needed). If it
+ * is not possible to create the directory, then the test case will fail.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.exists() && !aDir.isDirectory()) {
+ TestCase.fail("'" + aDir
+ + "' already exists and is not a directory");
+ }
+ if (aDir.exists()) {
+ return;
+ }
+ createDir(aDir.getParentFile());
+ TestCase.assertTrue("Could not create '" + aDir + "'", aDir.mkdir());
+ }
+
+ /**
+ * Gets the test output directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test output directory.
+ */
+ public static File getTestOutputDir(Class aTestClass) {
+ File file = getPath(TEST_OUTPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Gets the test input directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test input directory.
+ */
+ public static File getTestInputDir(Class aTestClass) {
+ File file = getPath(TEST_INPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Creates a directory hierarchy for the output directory of a test class if
+ * needed.
+ *
+ * @param aTestClass
+ * Test class
+ * @return Test directory.
+ */
+ public static File createTestOutputDir(Class aTestClass) {
+ File file = getTestOutputDir(aTestClass);
+ createDir(file);
+ return file;
+ }
+
+ /**
+ * Gets a test output file name. This returns a File object representing the
+ * output file and ensures that the directory where the file will be created
+ * already exists.
+ *
+ * @param aName
+ * Name of the file.
+ * @param aTestClass
+ * Test class.
+ * @return File.
+ */
+ public static File getTestOutputFile(String aName, Class aTestClass) {
+ File file = new File(getTestOutputDir(aTestClass), aName);
+ createDir(file.getParentFile());
+ return file;
+ }
+
+ public static String read(InputStream aIs) throws IOException {
+ try {
+ StringBuffer buffer = new StringBuffer();
+ int c;
+ while ((c = aIs.read()) != -1) {
+ buffer.append((char)c);
+ }
+ return buffer.toString();
+ } finally {
+ aIs.close();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the stream resource.
+ */
+public class StreamResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ File file = new File(
+ FileSystemUtils.getTestInputDir(StreamResourceTest.class),
+ "myresource.txt");
+ assertTrue(file.canRead());
+ InputStream fileIs = new FileInputStream(file);
+ InputResource resource = new StreamResource(fileIs);
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+
+
+/**
+ * Test resource for locating resources in the classpath.
+ */
+public class TestResource extends FileResource {
+
+ /**
+ * Test class name.
+ * @param aTestClass Test class.
+ * @param aName Name of the file to look for.
+ */
+ public TestResource(Class aTestClass, String aName) {
+ super(getFile(aTestClass, aName));
+ }
+
+ /**
+ * Computes the file path of the file to look for.
+ * @param aClass Test class name.
+ * @param aName Name of the file.
+ * @return File.
+ */
+ private static File getFile(Class aClass, String aName) {
+ File dir = FileSystemUtils.getTestInputDir(aClass);
+ return new File(dir, aName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jmock.Mock;
+import org.jmock.cglib.MockObjectTestCase;
+
+/**
+ * Test of the observer pattern implementation.
+ */
+public class ObservableTest extends MockObjectTestCase {
+
+ private static final int SUBSCRIBER_COUNT = 100;
+
+ private static final String UPDATE = "send";
+
+ private Observable<ObservableTest, String> _observable;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ _observable = new Observable<ObservableTest, String>(this,
+ new DefaultObserverNotifier());
+ }
+
+ /**
+ * Tests subscription and notification of one subscriber.
+ */
+ public void testOneObserver() {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ String message = "hallo";
+ mockObserver.expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+
+ _observable.send(message);
+ _observable.unsubscribe(subscription);
+ assertEquals(0, _observable.getObserverCount());
+
+ _observable.send(message);
+
+ }
+
+ /**
+ * Subscribes many susbcribers and sends notifications to subscribers.
+ * Verifies that unique subscription number are returned. Also verifies that
+ * the correct subscribers are notfied.
+ */
+ public void testManySubscribers() {
+ int nsubscribers = SUBSCRIBER_COUNT;
+ Mock[] mocks = new Mock[nsubscribers];
+
+ List<Long> subscriptions = new ArrayList<Long>();
+ for (int i = 0; i < nsubscribers; i++) {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ mocks[i] = mockObserver;
+ assertTrue(subscriptions.add(subscription));
+ }
+
+ assertEquals(nsubscribers, _observable.getObserverCount());
+
+ String message = "hallo";
+ for (int i = 0; i < nsubscribers; i++) {
+ mocks[i].expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+ }
+
+ _observable.send(message);
+
+ for (int i = nsubscribers / 2; i < nsubscribers; i++) {
+ _observable.unsubscribe(subscriptions.get(i));
+ }
+ assertEquals(nsubscribers - (nsubscribers - nsubscribers / 2),
+ _observable.getObserverCount());
+
+ message = "blabla";
+ for (int i = 0; i < nsubscribers / 2; i++) {
+ mocks[i].expects(once()).method(UPDATE).with(same(this),
+ eq(message));
+ }
+ _observable.send(message);
+ }
+
+ /**
+ * Subscribes and then unsubscribes with a wrong id. Verifies that
+ * IllegalArgumentException is thrown.
+ *
+ */
+ public void testUnsubscribeWithWrongSubscription() {
+ Mock mockObserver = mock(Observer.class);
+ Observer<ObservableTest, String> observer = (Observer<ObservableTest, String>) mockObserver
+ .proxy();
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ try {
+ _observable.unsubscribe(subscription + 1);
+ } catch (IllegalArgumentException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Useful assertions for use in test cases.
+ */
+public final class AssertionUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private AssertionUtils() {
+ // Empty
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aExpected
+ * Expected object array.
+ * @param aActual
+ * Actual object array.
+ */
+ public static void assertEquals(Object[] aExpected, Object[] aActual) {
+ assertEquals("", aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected array.
+ * @param aActual
+ * Actual array.
+ */
+ public static void assertEquals(String aMsg, Object[] aExpected,
+ Object[] aActual) {
+ TestCase.assertEquals(aMsg + ": Array lengths ", aExpected.length,
+ aActual.length);
+
+ for (int i = 0; i < aExpected.length; i++) {
+ TestCase.assertEquals(aMsg + ": Element " + i, aExpected[i],
+ aActual[i]);
+ }
+ }
+
+ /**
+ * Asserts that two objects are equal, and in case the object is an Object[]
+ * delegates to {@link #assertEquals(String, Object[], Object[]).
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static void assertEquals(String aMsg, Object aExpected,
+ Object aActual) {
+ if (aExpected instanceof Object[]) {
+ AssertionUtils.assertEquals(aMsg, (Object[]) aExpected,
+ (Object[]) aActual);
+
+ return;
+ }
+
+ TestCase.assertEquals(aMsg, aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two maps are equal by comparing all keys and by checking
+ * that the values for the same keys are the same.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpectedMap
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static void assertEquals(String aMsg, Map aExpectedMap, Map aActual) {
+ TestCase.assertEquals("Map sizes differ", aExpectedMap.size(), aActual
+ .size());
+
+ Set keys = aExpectedMap.keySet();
+
+ for (Iterator i = keys.iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ TestCase.assertTrue("Map does not containg entry for key:" + key,
+ aActual.containsKey(key));
+ AssertionUtils.assertEquals("Value of key " + key + " of map",
+ aExpectedMap.get(key), aActual.get(key));
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tracks the occurence of certain events in a test environment. Threads in a
+ * test environment tell the event tracker of the occurrence of certain events
+ * using {@link #eventOccurred(Event)}. Test code inspects the events sent by a
+ * thread using {@link #isEventSent(Thread, Event)}.
+ *
+ * A record is kept of every event which is sent. Therefore, the occurrence of a
+ * new event does not erase a previously sent event.
+ *
+ * @param <Event>
+ * Type of event sent from test code. Usually String will be
+ * sufficient. The event type must provide a sensible implementation
+ * of {@link java.lang.Object#equals(java.lang.Object)}.
+ */
+public class EventTracker<Event> {
+
+ private static final Log LOG = LogFactory.getLog(EventTracker.class);
+
+ /**
+ * Map of Thread object to a list of events.
+ */
+ private Map<Thread, List<Event>> _events;
+
+ /**
+ * Constructs the event tracker.
+ *
+ */
+ public EventTracker() {
+ _events = new HashMap<Thread, List<Event>>();
+ }
+
+ /**
+ * Called by a thread to inform that an event has occurred.
+ *
+ * @param aEvent
+ * Event that was sent.
+ */
+ public synchronized void eventOccurred(Event aEvent) {
+ LOG.info("Event '" + aEvent + "' sent.");
+ Thread current = Thread.currentThread();
+ List<Event> events = _events.get(current);
+ if (events == null) {
+ events = new ArrayList<Event>();
+ _events.put(current, events);
+ }
+ events.add(aEvent);
+ }
+
+ /**
+ * Checks if a specific event has happened in a specific thread.
+ *
+ * @param aThread
+ * Thread to check.
+ * @param aEvent
+ * Event to check for.
+ * @return Whether or not the event was sent.
+ */
+ public synchronized boolean isEventSent(Thread aThread, Event aEvent) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ return false;
+ }
+ return events.contains(aEvent);
+ }
+
+ /**
+ * Gets the events for a thread in the order they were sent
+ *
+ * @param aThread
+ * Thread to get events for.
+ * @return Events that were sent. A zero-sized array is returned if no
+ * events were sent.
+ */
+ public synchronized List<Event> getEvents(Thread aThread) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ events = Collections.emptyList();
+ }
+ return Collections.unmodifiableList(events);
+ }
+
+ /**
+ * Gets the number of times an event was sent summed up
+ * over all threads.
+ *
+ * @param aEvent
+ * Event to check.
+ * @return Number of times it was reached.
+ */
+ public synchronized int getEventCount(Event aEvent) {
+ int count = 0;
+ for (Thread thread : _events.keySet()) {
+ List<Event> events = _events.get(thread);
+ for (Event event : events) {
+ if (event.equals(aEvent)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+
+/**
+ * Exporting the hibernate mapping.
+ */
+public final class HibernateExporter {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateExporter() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(aArgs[1]);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaExport export = new SchemaExport(conf);
+ export.setDelimiter(";");
+ export.setOutputFile(file);
+ export.create(true, false);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaUpdate;
+
+/**
+ * Exporting the hibernate mapping.
+ */
+public final class HibernateUpdater {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateUpdater() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(file);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaUpdate lSchemaUpdate = new SchemaUpdate(conf);
+ lSchemaUpdate.execute(true, true);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.oro.io.AwkFilenameFilter;
+import org.hibernate.cfg.Configuration;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * Hibernate utilities.
+ */
+public final class HibernateUtils {
+
+ private static final String DATABASE_PROPS = "test.database.properties";
+
+ /**
+ * Disabled.
+ *
+ */
+ private HibernateUtils() {
+ // Empty
+ }
+
+ /**
+ * @param aDir
+ * @return
+ */
+ public static Configuration getConfiguration(File aDir) throws IOException {
+ Configuration conf = new Configuration();
+ File[] files = aDir.listFiles((FileFilter) (new AwkFilenameFilter(
+ ".*\\.hbm\\.xml")));
+ for (File f : files) {
+ System.out.println("Mapping file: " + f);
+ conf.addFile(f);
+ }
+
+ Map<String, String> dbProps = getHibernateProperties();
+
+ for (Map.Entry<String, String> entry : dbProps.entrySet()) {
+ System.out.println("Property: " + entry.getKey() + "="
+ + entry.getValue());
+ conf.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ return conf;
+ }
+
+ private static Map<String, String> getHibernateProperties()
+ throws IOException {
+
+ System.out.println("Reading properties file: " + DATABASE_PROPS);
+ InputResource lPropFile = new ClassPathResource(DATABASE_PROPS);
+ Properties props = new Properties();
+ props.load(lPropFile.getInputStream());
+
+ Map<String, String> result = new TreeMap<String, String>();
+ result.put("hibernate.connection.driver_class", props
+ .getProperty("database.driver"));
+ result.put("hibernate.connection.url", props
+ .getProperty("database.url"));
+ result.put("hibernate.connection.username", props
+ .getProperty("database.username"));
+ result.put("hibernate.connection.password", props
+ .getProperty("database.password"));
+
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Spring configuration files to use.
+ */
+public class SpringConfigFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of Spring config files.
+ *
+ */
+ public SpringConfigFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public SpringConfigFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.operation.DatabaseOperation;
+import org.hibernate.SessionFactory;
+import org.jmock.cglib.MockObjectTestCase;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ * Test case support class for spring tests.
+ */
+public class SpringTestCase extends MockObjectTestCase {
+
+ private static final Log LOG = LogFactory.getLog(SpringTestCase.class);
+
+ /**
+ * Session factory bean name.
+ */
+ private static final String SESSION_FACTORY = "sessionFactory";
+
+ /**
+ * Data source bean name.
+ */
+ private static final String DATA_SOURCE = "dataSource";
+
+ /**
+ * Transaction manager bean name.
+ */
+ private static final String TRANSACTION_MANAGER = "transactionManager";
+
+ /**
+ * Name of the ConfigFileList bean that describes the Hibernate mapping
+ * files to use.
+ */
+ private static final String HIBERNATE_CONFIG_FILES = "hibernateMappingFiles";
+
+ /**
+ * Schema pattern.
+ */
+ private static final String SCHEMA_PATTERN = "%";
+
+ /**
+ * List of (String) configuration file locations for spring.
+ */
+ private String[] _configLocations;
+
+ /**
+ * Application context for storing bean definitions that vary on a test by
+ * test basis and cannot be hardcoded in the spring configuration files.
+ */
+ private GenericApplicationContext _parentContext;
+
+ /**
+ * Cached spring application context.
+ */
+ private ApplicationContext _context;
+
+ public SpringTestCase(Class<? extends SpringConfigFiles> aSpringFiles,
+ Class<? extends HibernateMappingFiles> aMappingFiles) {
+ try {
+ SpringConfigFiles springFiles = aSpringFiles.newInstance();
+ _configLocations = springFiles.toArray(new String[0]);
+ } catch (Exception e) {
+ fail("Could not construct spring config files class '"
+ + aSpringFiles.getName() + "'");
+ }
+
+ // Register the Hibernate mapping files as a bean.
+ _parentContext = new GenericApplicationContext();
+ BeanDefinition lDefinition = new RootBeanDefinition(aMappingFiles);
+ _parentContext.registerBeanDefinition(HIBERNATE_CONFIG_FILES,
+ lDefinition);
+ _parentContext.refresh();
+
+ }
+
+ /**
+ * Gets the spring context.
+ *
+ * @return Spring context.
+ */
+ protected synchronized ApplicationContext getSpringContext() {
+ if (_context == null) {
+ _context = new ClassPathXmlApplicationContext(
+ (String[]) _configLocations, _parentContext);
+ assertNotNull(_context);
+ }
+ return _context;
+ }
+
+ /**
+ * @return Hibernate session factory.
+ */
+ protected SessionFactory getSessionFactory() {
+ SessionFactory factory = (SessionFactory) getSpringContext().getBean(
+ SESSION_FACTORY);
+ assertNotNull(factory);
+ return factory;
+ }
+
+ protected void setUp() throws Exception {
+ LOG.info("Performing setUp()");
+
+ super.setUp();
+
+ _context = null; // make sure we get a new application context for
+ // every
+ // new test.
+
+ BeanKernel.overrideBeanFactory(new TestSpringBeanFactory(
+ getSpringContext()));
+
+ cleanDatabase();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ super.tearDown();
+ } finally {
+ LOG.info("tearDown() complete");
+ }
+ }
+
+ /**
+ * @return Transaction manager
+ */
+ protected PlatformTransactionManager getTransactionManager() {
+ PlatformTransactionManager manager = (PlatformTransactionManager) getSpringContext()
+ .getBean(TRANSACTION_MANAGER);
+ assertNotNull(manager);
+ return manager;
+ }
+
+ /**
+ * @return Starts a new transaction.
+ */
+ protected TransactionStatus getTransaction() {
+ DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+ def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+ return getTransactionManager().getTransaction(def);
+ }
+
+ /**
+ * Returns the hibernate template for executing hibernate-specific
+ * functionality.
+ *
+ * @return Hibernate template.
+ */
+ protected HibernateTemplate getTemplate() {
+ HibernateTemplate template = (HibernateTemplate) getSpringContext()
+ .getBean(HibernateTemplate.class.getName());
+ assertNotNull(template);
+ return template;
+ }
+
+ /**
+ * Flushes the session. Should be called after some Hibernate work and
+ * before JDBC is used to check results.
+ *
+ */
+ protected void flush() {
+ getTemplate().flush();
+ }
+
+ /**
+ * Flushes the session first and then removes all objects from the Session
+ * cache. Should be called after some Hibernate work and before JDBC is used
+ * to check results.
+ *
+ */
+ protected void clear() {
+ flush();
+ getTemplate().clear();
+ }
+
+ /**
+ * Evicts the object from the session. This is essential for the
+ * implementation of unit tests where first an object is saved and is
+ * retrieved later. By removing the object from the session, Hibernate must
+ * retrieve the object again from the database.
+ *
+ * @param aObject
+ */
+ protected void evict(Object aObject) {
+ getTemplate().evict(aObject);
+ }
+
+ /**
+ * Gets the connection.
+ *
+ * @return Connection.
+ */
+ public Connection getConnection() {
+ return DataSourceUtils.getConnection(getDataSource());
+ }
+
+ public void cleanDatabase() throws SQLException {
+
+ if (!isDatabaseConfigured()) {
+ return;
+ }
+
+ String[] tables = getTableNames();
+
+ try {
+ IDatabaseConnection connection = new DatabaseConnection(
+ getConnection());
+ ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
+ IDataSet dataset = new FilteredDataSet(filter, connection
+ .createDataSet(tables));
+
+ DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+ } catch (DatabaseUnitException e) {
+ SQLException exc = new SQLException(e.getMessage());
+ exc.initCause(e);
+ throw exc;
+ }
+ }
+
+ /**
+ * @throws SQLException
+ */
+ public String[] getTableNames() throws SQLException {
+
+ List<String> result = new ArrayList<String>();
+ LOG.debug("Getting database table names to clean (schema: '"
+ + SCHEMA_PATTERN + "'");
+
+ ResultSet tables = getConnection().getMetaData().getTables(null,
+ SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+ while (tables.next()) {
+ String table = tables.getString("TABLE_NAME");
+ // Make sure we do not touch hibernate's specific
+ // infrastructure tables.
+ if (!table.toLowerCase().startsWith("hibernate")) {
+ result.add(table);
+ LOG.debug("Adding " + table
+ + " to list of tables to be cleaned.");
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTables(List aTableList) throws SQLException {
+ Iterator liTable = aTableList.iterator();
+ while (liTable.hasNext()) {
+ emptyTable((String) liTable.next());
+ }
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTable(String aTable) throws SQLException {
+ executeSql("delete from " + aTable);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void dropTable(String aTable) throws SQLException {
+ executeQuery("drop table " + aTable);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction.
+ *
+ * @param aSql
+ * SQL statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql) {
+ return executeSql(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL statement.
+ * @param aArg
+ * Argument of the sql statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql, final Object aArg) {
+ return executeSql(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes an sql statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL query to execute.
+ * @param aArgs
+ * Arguments.
+ * @return Number of rows updated.
+ */
+ public int executeSql(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ JdbcTemplate template = new JdbcTemplate(getDataSource());
+ int result = template.update(aSql, aArgs);
+
+ Map<String, Integer> map = new TreeMap<String, Integer>();
+ map.put("result", new Integer(result));
+
+ return map;
+ }
+ });
+
+ return ((Integer) results.get("result")).intValue();
+ }
+
+ /**
+ * Executes a transaction with a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work.
+ * @return Result.
+ */
+ public Object executeTransaction(TransactionCallback aCallback) {
+ TransactionTemplate lTemplate = new TransactionTemplate(
+ getTransactionManager());
+ return lTemplate.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction without a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work. .
+ */
+ protected void executeTransaction(TransactionCallbackWithoutResult aCallback) {
+ TransactionTemplate template = new TransactionTemplate(
+ getTransactionManager());
+ template.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ * @return Result.
+ */
+ public Map executeTransaction(final TestTransactionCallback aCallback) {
+ return (Map) executeTransaction(new TransactionCallback() {
+ public Object doInTransaction(TransactionStatus aArg) {
+ try {
+ return aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ */
+ public void executeTransaction(
+ final TestTransactionCallbackWithoutResult aCallback) {
+ executeTransaction(new TransactionCallbackWithoutResult() {
+ public void doInTransactionWithoutResult(TransactionStatus aArg) {
+ try {
+ aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes an SQL query within a transaction.
+ *
+ * @param aSql
+ * Query to execute.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql) {
+ return executeQuery(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes a query with a single argument. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Query.
+ * @param aArg
+ * Argument.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql, Object aArg) {
+ return executeQuery(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes a query within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Sql query.
+ * @param aArgs
+ * Arguments to the query.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ Connection connection = getConnection();
+
+ PreparedStatement statement = connection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ ResultSet resultSet = statement.executeQuery();
+ TreeMap results = new TreeMap();
+ results.put("resultSet", resultSet);
+
+ return results;
+ }
+ });
+
+ return (ResultSet) results.get("resultSet");
+ }
+
+ /**
+ * Sets the values of a prepared statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aArgs
+ * Arguments to the prepared statement.
+ * @param aStatement
+ * Prepared statement
+ * @throws SQLException
+ */
+ private void setPreparedParams(final Object[] aArgs,
+ PreparedStatement aStatement) throws SQLException {
+ for (int i = 1; i <= aArgs.length; i++) {
+ setPreparedParam(i, aStatement, aArgs[i - 1]);
+ }
+ }
+
+ /**
+ * Sets a prepared statement parameter.
+ *
+ * @param aIndex
+ * Index of the parameter.
+ * @param aStatement
+ * Prepared statement.
+ * @param aObject
+ * Value Must be of type Integer, Long, or String. TODO extend
+ * with more types of values.
+ * @throws SQLException
+ */
+ private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+ Object aObject) throws SQLException {
+ if (aObject instanceof Integer) {
+ aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+ } else if (aObject instanceof Long) {
+ aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+ } else if (aObject instanceof String) {
+ aStatement.setString(aIndex, (String) aObject);
+ } else {
+ TestCase.fail("Unsupported object type for prepared statement: "
+ + aObject.getClass() + " value: " + aObject
+ + " statement: " + aStatement);
+ }
+ }
+
+ private boolean isDatabaseConfigured() {
+ try {
+ getDataSource();
+ } catch (NoSuchBeanDefinitionException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return Returns the dataSource.
+ */
+ public DataSource getDataSource() {
+ DataSource ds = (DriverManagerDataSource) getSpringContext().getBean(
+ DATA_SOURCE);
+ assertNotNull(ds);
+ return ds;
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ protected int getTableSize(String aTable) throws SQLException {
+ ResultSet resultSet = executeQuery("select * from " + aTable);
+ int count = 0;
+
+ while (resultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+ protected int countResultSet(ResultSet aResultSet) throws SQLException {
+ int count = 0;
+
+ while (aResultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.general.BeanFactoryException;
+
+/**
+ * Bean factory which uses Spring.
+ */
+public class TestSpringBeanFactory implements BeanFactory {
+
+ private ApplicationContext _context;
+
+ public TestSpringBeanFactory(ApplicationContext aContext) {
+ _context = aContext;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String, java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _context.getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import junit.framework.Assert;
+
+/**
+ * @author Erik Test support utility.
+ */
+public final class TestSupport {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TestSupport() {
+ // Empty
+ }
+
+ /**
+ * Obtain root directory of JUnit tests.
+ *
+ * @return Directory name.
+ */
+ public static File getTestRootDir() {
+ return new File("testdata");
+ }
+
+ /**
+ * Returns a temporary directory.
+ *
+ * @return Temporary directory.
+ */
+ public static File getTmpDir() {
+ return new File(getTestRootDir(), "tmpdir");
+ }
+
+ /**
+ * Recursively remove a directory.
+ *
+ * @param aSrc
+ * Directoryto remove.
+ */
+ public static void removeDir(File aSrc) {
+ if (!aSrc.exists()) {
+ return;
+ }
+ Assert.assertTrue(aSrc.getPath(), aSrc.isDirectory());
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ removeDir(file);
+ } else {
+ Assert.assertTrue(file.getPath(), file.delete());
+ }
+ }
+ Assert.assertTrue(aSrc.getPath(), aSrc.delete());
+ }
+
+ /**
+ * Recursively copy a directory.
+ *
+ * @param aSrc
+ * Source directory
+ * @param aTarget
+ * Target directory.
+ */
+ public static void copyDir(File aSrc, File aTarget) {
+ Assert.assertTrue(aSrc.isDirectory());
+ Assert.assertTrue(!aTarget.exists());
+
+ aTarget.mkdirs();
+
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ if (!file.getName().equals(".svn")) {
+ copyDir(new File(aSrc, file.getName()), new File(aTarget,
+ file.getName()));
+ }
+ } else {
+ copyFile(file, new File(aTarget, file.getName()));
+ }
+ }
+ }
+
+ /**
+ * Copy a file. If copying fails then the testcase will fail.
+ *
+ * @param aSrc
+ * Source file.
+ * @param aTarget
+ * Target file.
+ */
+ public static void copyFile(File aSrc, File aTarget) {
+
+ try {
+ FileInputStream fis = new FileInputStream(aSrc);
+ FileOutputStream fos = new FileOutputStream(aTarget);
+ FileChannel fcin = fis.getChannel();
+ FileChannel fcout = fos.getChannel();
+
+ // map input file
+
+ MappedByteBuffer mbb = fcin.map(FileChannel.MapMode.READ_ONLY, 0,
+ fcin.size());
+
+ // do the file copy
+ fcout.write(mbb);
+
+ // finish up
+
+ fcin.close();
+ fcout.close();
+ fis.close();
+ fos.close();
+ } catch (IOException e) {
+ Assert.assertTrue("Copying file " + aSrc.getPath() + " to "
+ + aTarget.getPath() + " failed.", false);
+ }
+ }
+
+ /**
+ * Remove a file or directory. The test case will fail if this does not
+ * succeed.
+ *
+ * @param aFile
+ * entry to remove.
+ */
+ public static void delete(File aFile) {
+ Assert
+ .assertTrue("Could not delete " + aFile.getPath(), aFile
+ .delete());
+ }
+
+ /**
+ * Remove all files within a given directory including the directory itself.
+ * This only attempts to remove regular files and not directories within the
+ * directory. If the directory contains a nested directory, the deletion
+ * will fail. The test case will fail if this fails.
+ *
+ * @param aDir
+ * Directory to remove.
+ */
+ public static void deleteDir(File aDir) {
+ cleanDir(aDir);
+ delete(aDir);
+ }
+
+ /**
+ * Remove all regular files within a given directory.
+ *
+ * @param outputDirName
+ */
+ public static void cleanDir(File aDir) {
+ if (!aDir.exists()) {
+ return; // nothing to do.
+ }
+ File[] entries = aDir.listFiles();
+ for (int i = 0; i < entries.length; i++) {
+ File file = entries[i];
+ if (file.isFile()) {
+ Assert.assertTrue("Could not delete " + entries[i].getPath(),
+ entries[i].delete());
+ }
+ }
+ }
+
+ /**
+ * Creates directory if it does not already exist. The test case will fail
+ * if the directory cannot be created.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.isDirectory()) {
+ return; // nothing to do.
+ }
+ Assert.assertTrue("Could not create directory " + aDir.getPath(), aDir
+ .mkdirs());
+ }
+}
--- /dev/null
+package org.wamblee.test;
+
+import java.util.Map;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ */
+public interface TestTransactionCallback {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @return A map containg the resuls of the execution. This is a convenient
+ * method of returning multiple results from a call.
+ */
+ Map execute() throws Exception;
+}
--- /dev/null
+package org.wamblee.test;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ */
+public interface TestTransactionCallbackWithoutResult {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ */
+ void execute() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import junit.framework.TestCase;
+
+/**
+ * Timing utilities.
+ */
+public final class TimingUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TimingUtils() {
+ // Empty
+ }
+
+ /**
+ * Sleeps for a time.
+ * @param aMillis Number of milliseconds to sleep.
+ */
+ public static void sleep(int aMillis) {
+ try {
+ Thread.sleep(aMillis);
+ } catch (InterruptedException e) {
+ TestCase.fail("Who interrupted my sleep?");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+import org.springframework.core.io.ClassPathResource;
+import org.wamblee.io.FileSystemUtils;
+
+/**
+ * Tests for {@link org.wamblee.xml.ClasspathUriResolver}.
+ */
+public class ClasspathUriResolverTest extends TestCase {
+
+ private URIResolver _resolver;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _resolver = new ClasspathUriResolver();
+ }
+
+ /**
+ * Resolves an existing file. Verifies the file is resolved correctly.
+ * @throws TransformerException
+ * @throws IOException
+ */
+ public void testResolveExistingFile() throws TransformerException, IOException {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml.xsl", "");
+ assertTrue(source instanceof StreamSource);
+ String resolved = FileSystemUtils.read(((StreamSource)source).getInputStream());
+
+ ClassPathResource resource = new ClassPathResource("org/wamblee/xml/reportToHtml.xsl");
+ String expected = FileSystemUtils.read(resource.getInputStream());
+ assertEquals(expected, resolved);
+ }
+
+ /**
+ * Resolves a non-existing file. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testResolveNonExistingFile() {
+ try {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml-nonexisting.xsl", "");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import junit.framework.TestCase;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.Element;
+
+/**
+ * XML test support utilities.
+ */
+public final class XmlUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private XmlUtils() {
+ // Empty
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg,
+ org.w3c.dom.Document aExpected, org.w3c.dom.Document aActual) {
+ assertEquals(aMsg, DomUtils.convert(aExpected), DomUtils
+ .convert(aActual));
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Document aExpected,
+ Document aActual) {
+ assertEquals(aMsg + "/" + aExpected.getRootElement().getName(), aExpected.getRootElement(), aActual.getRootElement());
+ }
+
+ /**
+ * Checks equality of two XML elements excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Element aExpected,
+ Element aActual) {
+
+ // Name.
+ TestCase.assertEquals(aMsg + "/name()", aExpected.getName(), aActual
+ .getName());
+
+ // Text
+ TestCase.assertEquals(aMsg + "/text()", aExpected.getTextTrim(),
+ aActual.getTextTrim());
+
+ // Attributes
+ List<Attribute> expectedAttrs = aExpected.attributes();
+ Collections.sort(expectedAttrs, new AttributeComparator());
+ List<Attribute> actualAttrs = aActual.attributes();
+ Collections.sort(actualAttrs, new AttributeComparator());
+
+ TestCase.assertEquals("count(" + aMsg + "/@*)", expectedAttrs.size(),
+ actualAttrs.size());
+ for (int i = 0; i < expectedAttrs.size(); i++) {
+ String msg = aMsg + "/@" + expectedAttrs.get(i).getName();
+ assertEquals(msg, expectedAttrs.get(i), actualAttrs.get(i));
+ }
+
+ // Nested elements.
+ List<Element> expectedElems = aExpected.elements();
+ List<Element> actualElems = aActual.elements();
+ TestCase.assertEquals("count(" + aMsg + "/*)", expectedElems.size(),
+ actualElems.size());
+ // determine the how-manyth element of the given name we are at.
+ // Maps element name to the last used index (or null if not yet used)
+ Map<String, Integer> elementIndex = new TreeMap<String, Integer>();
+ for (int i = 0; i < expectedElems.size(); i++) {
+ String elemName = expectedElems.get(i).getName();
+ Integer index = elementIndex.get(elemName);
+ if (index == null) {
+ index = 1;
+ } else {
+ index++;
+ }
+ elementIndex.put(elemName, index);
+ String msg = aMsg + "/" + expectedElems.get(i).getName() + "["
+ + index + "]";
+
+ assertEquals(msg, expectedElems.get(i), actualElems.get(i));
+ }
+ }
+
+ /**
+ * Checks equality of two attributes. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Attribute aExpected,
+ Attribute aActual) {
+ TestCase.assertEquals(aMsg + ":name", aExpected.getName(), aActual
+ .getName());
+ TestCase.assertEquals(aMsg + ":value", aExpected.getValue(), aActual
+ .getValue());
+ }
+
+ /**
+ * Comparator which compares attributes by name.
+ */
+ private static final class AttributeComparator implements
+ Comparator<Attribute> {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Comparator#compare(T, T)
+ */
+ public int compare(Attribute aAttribute1, Attribute aAttribute2) {
+ return aAttribute1.getName().compareTo(aAttribute2.getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.FileSystemUtils;
+import org.wamblee.io.InputResource;
+
+import org.w3c.dom.Document;
+
+/**
+ * Tests the XSL transformer.
+ */
+public class XslTransformerTest extends TestCase {
+
+ private static final String INCLUDED_XSL_FILE = "utilities.xsl";
+
+ private static final String REPORT_XML = "report.xml";
+
+ private static final String REPORT_TO_HTML_XSLT = "reportToHtml.xsl";
+
+ private static final String REPORT_TO_HTML2_XSLT = "reportToHtml2.xsl";
+
+ private static final String REPORT_TO_HTML_INVALID_XSLT = "reportToHtml-invalid.xsl";
+
+ private static final String REPORT_TO_HTML_NONWELLFORMED_XSLT = "reportToHtml-nonwellformed.xsl";
+
+ private static final String REPORT_TO_TEXT_XSLT = "reportToText.xsl";
+
+ private String getResourcePath(String aResource) {
+ return getClass().getPackage().getName().replaceAll("\\.", "/") + "/" + aResource;
+ }
+
+ /**
+ * Transforms a file while using the default resolver, where the included
+ * file can be found. Verifies the transformation is done correctly.
+ *
+ */
+ public void testTransformUsingDefaultResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new ClassPathResource(getResourcePath(REPORT_XML));
+
+ Source xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ Document document = builder.parse(xmlResource.getInputStream());
+ Source documentSource = new StreamSource(xmlResource.getInputStream());
+
+ Document expected = DomUtils.read(new ClassPathResource(getResourcePath(
+ "output-reportToHtml-report.xml")).getInputStream());
+
+ Document output1 = transformer.transform(documentData, xslt);
+ XmlUtils.assertEquals("byte[] transform", expected, output1);
+
+ xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+ Document output2 = transformer.transform(document, xslt);
+ XmlUtils.assertEquals("document transform", expected, output2);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Result output = new StreamResult(os);
+
+ xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+ transformer.transform(documentSource, output, xslt);
+ XmlUtils.assertEquals("document source transform", expected, DomUtils
+ .read(os.toString()));
+
+ xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+ String result = transformer.textTransform(documentData, xslt);
+ XmlUtils
+ .assertEquals("text transform", expected, DomUtils.read(result));
+ }
+
+ /**
+ * Transforms a file using the default resolver where the included file
+ * cannot be found. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testTransformUsingDefaultResolverFails() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource =
+ new ClassPathResource(getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML2_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using an invalid Xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformInvalidXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new ClassPathResource(
+ getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(REPORT_TO_HTML_INVALID_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a non-well formed xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformNonWellformedXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new ClassPathResource(
+ getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_NONWELLFORMED_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a class path resolver.
+ *
+ */
+ public void testTransformUsingClassPathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new ClassPathResource(getResourcePath(
+ REPORT_XML));
+ Source xslt = new StreamSource(new ClassPathResource(
+ getResourcePath(REPORT_TO_HTML2_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ Document output1 = transformer.transform(documentData, xslt);
+ Document expected = DomUtils.read(new ClassPathResource(
+ getResourcePath("output-reportToHtml-report.xml"))
+ .getInputStream());
+ XmlUtils.assertEquals("doc", expected, output1);
+ }
+
+ /**
+ * Transforms a file to text output. Verifies the file is transformed
+ * correctly.
+ *
+ */
+ public void testTransformToTextOutput() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new ClassPathResource(
+ getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(REPORT_TO_TEXT_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ String result = transformer.textTransform(documentData, xslt);
+ String expected = "Hello world!";
+ assertEquals("text transform", expected, result);
+ }
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver where the file does not exist.
+ *
+ */
+ public void testResolveWithDefaultResolverFileNotFound() {
+ XslTransformer transformer = new XslTransformer();
+ try {
+ Source source = transformer.resolve("org/wamblee/xml/utilities-nonexistent.xsl");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver.
+ *
+ */
+ public void testResolveWithClasspathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+ Source source = transformer.resolve(getResourcePath(INCLUDED_XSL_FILE));
+ assert(source instanceof StreamSource);
+ StreamSource ssource = (StreamSource)source;
+ String data = FileSystemUtils.read(ssource.getInputStream());
+ String expected = FileSystemUtils.read(new ClassPathResource(getResourcePath(INCLUDED_XSL_FILE)).getInputStream());
+ assertEquals(expected, data);
+ }
+
+}
+
+
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="test"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="test"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org/wamblee/general/spring1.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactoryBla
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactory
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <bean id="java.lang.String"
+ class="java.lang.String">
+ <constructor-arg><value>hello</value></constructor-arg>
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+This is my resource
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <h2>Successfully recorded programs <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:25 - 00:10: <strong>Wintertijd</strong> (Nederland
+ 1/Documentaire)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Some description MINSK - De presidentsverkiezingen
+ in Wit-Rusland zijn zondag met ruime cijfers gewonnen door
+ zittend president Aleksandr Loekasjenko. Dat bleek zondag uit
+ exitpolls uitgevoerd in opdracht van het totalitaire regime. Het
+ staatshoofd zou kunnen rekenen op ruim 82 procent van de
+ stemmen. Volgens de eerste gedeeltelijke uitslagen zou
+ Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p></h2>
+ <h2>Possibly interesting programs</h2>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Brainiac</strong> (Discovery Channel/science)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Humor</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ <h3>Category: horror</h3>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Andere tijden</strong> (Nederland 1/docu)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Documentaire</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ </body>
+</html>
--- /dev/null
+<report>
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifdd test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:ifdd>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifx test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:include href="org/wamblee/xml/utilities.xsl"/>
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:template match="report">
+ <xsl:text>Hello world!</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+This is the utilities project of wamblee.org. It contains various utilities
+and useful programs for various purposes. The most important part is the
+support library.
+
--- /dev/null
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
--- /dev/null
+Support utilities for enterprise development
+============================================
+
+* support: Base (non-Java EE) support
+* system: An IOC framework experiment. This should be flexible enough as a basis for
+ a CDI implementation. It currently offers only a programmatic API and does not use
+ annotations.
+* hibernate-jpa: Hibernate JPA dependencies
+* security: Security support utlities (user management and authorization)
+* test: Test support for JEE testing. In particular, junit testing with an inmemory or
+ external database and support for JPA testing with several JPA providers.
+
+
+To be removed from this project (currently skipped from the release)
+====================================================================
+
+* socketproxy: An old socketproxy. To be removed from the utilities
+* mythtv: An application for exposing mythtv recordings in a useful directory structure
+ This will be removed from the utilities
+
+To be added to this project
+===========================
+* utilities for CDI
+* wicket utilities.
+
+Building
+========
+
+You need maven2 and java 6
+
+Releasing
+=========
+
+Trigger release settings by building with -Dwamblee.release=true
+This will skip some projects that are not to be released.
+
+
--- /dev/null
+
+* refactor: Create jpa subproject with separate subprojects for api, hibernate,
+ toplink-essentials and eclipselink
+ Each subproject contains the required repo settings
+* Maven profile for nonrelease which is dactivated when release is active.
+* Have toplink, eclipselink, hibernate profiles that are deactivated by
+ release and active by default.
+
+* add cdi subproject to the utils
+* add schemagen for user management part.
+* JPA 2.0 implementation for security.
+
--- /dev/null
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">\r
+<xsl:output method="html" indent="yes"/>\r
+<xsl:decimal-format decimal-separator="." grouping-separator="," />\r
+\r
+<!-- Checkstyle XML Style Sheet by Stephane Bailliez <sbailliez@apache.org> -->\r
+<!-- Part of the Checkstyle distribution found at http://checkstyle.sourceforge.net -->\r
+<!-- Usage (generates checkstyle_report.html): -->\r
+<!-- <checkstyle failonviolation="false" config="${check.config}"> -->\r
+<!-- <fileset dir="${src.dir}" includes="**/*.java"/> -->\r
+<!-- <formatter type="xml" toFile="${doc.dir}/checkstyle_report.xml"/> -->\r
+<!-- </checkstyle> -->\r
+<!-- <style basedir="${doc.dir}" destdir="${doc.dir}" -->\r
+<!-- includes="checkstyle_report.xml" -->\r
+<!-- style="${doc.dir}/checkstyle-noframes.xsl"/> -->\r
+\r
+<xsl:template match="checkstyle">\r
+ <html>\r
+ <head>\r
+ <style type="text/css">\r
+ .bannercell {\r
+ border: 0px;\r
+ padding: 0px;\r
+ }\r
+ body {\r
+ margin-left: 10;\r
+ margin-right: 10;\r
+ font:normal 80% arial,helvetica,sanserif;\r
+ background-color:#FFFFFF;\r
+ color:#000000;\r
+ }\r
+ .a td { \r
+ background: #efefef;\r
+ }\r
+ .b td { \r
+ background: #fff;\r
+ }\r
+ th, td {\r
+ text-align: left;\r
+ vertical-align: top;\r
+ }\r
+ th {\r
+ font-weight:bold;\r
+ background: #ccc;\r
+ color: black;\r
+ }\r
+ table, th, td {\r
+ font-size:100%;\r
+ border: none\r
+ }\r
+ table.log tr td, tr th {\r
+ \r
+ }\r
+ h2 {\r
+ font-weight:bold;\r
+ font-size:140%;\r
+ margin-bottom: 5;\r
+ }\r
+ h3 {\r
+ font-size:100%;\r
+ font-weight:bold;\r
+ background: #525D76;\r
+ color: white;\r
+ text-decoration: none;\r
+ padding: 5px;\r
+ margin-right: 2px;\r
+ margin-left: 2px;\r
+ margin-bottom: 0;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <a name="top"></a>\r
+ <!-- jakarta logo -->\r
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">\r
+ <tr>\r
+ <td class="bannercell" rowspan="2">\r
+ <!--a href="http://jakarta.apache.org/">\r
+ <img src="http://jakarta.apache.org/images/jakarta-logo.gif" alt="http://jakarta.apache.org" align="left" border="0"/>\r
+ </a-->\r
+ </td>\r
+ <td class="text-align:right"><h2>CheckStyle Audit</h2></td>\r
+ </tr>\r
+ <tr>\r
+ <td class="text-align:right">Designed for use with <a href='http://checkstyle.sourceforge.net/'>CheckStyle</a> and <a href='http://jakarta.apache.org'>Ant</a>.</td>\r
+ </tr>\r
+ </table>\r
+ <hr size="1"/>\r
+ \r
+ <!-- Summary part -->\r
+ <xsl:apply-templates select="." mode="summary"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- Package List part -->\r
+ <xsl:apply-templates select="." mode="filelist"/>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ <!-- For each package create its part -->\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:apply-templates select="."/>\r
+ <p/>\r
+ <p/>\r
+ </xsl:for-each>\r
+ <hr size="1" width="100%" align="left"/>\r
+ \r
+ \r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+ \r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="filelist"> \r
+ <h3>Files</h3>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Name</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <xsl:for-each select="file">\r
+ <xsl:sort select="@name"/>\r
+ <xsl:variable name="errorCount" select="count(error)"/> \r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><a href="#f-{@name}"><xsl:value-of select="@name"/></a></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table> \r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="file">\r
+ <a name="f-{@name}"></a>\r
+ <h3>File <xsl:value-of select="@name"/></h3>\r
+ \r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Error Description</th>\r
+ <th>Line</th>\r
+ </tr>\r
+ <xsl:for-each select="error">\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="@message"/></td>\r
+ <td><xsl:value-of select="@line"/></td>\r
+ </tr>\r
+ </xsl:for-each>\r
+ </table>\r
+ <a href="#top">Back to top</a>\r
+ </xsl:template>\r
+ \r
+ \r
+ <xsl:template match="checkstyle" mode="summary">\r
+ <h3>Summary</h3>\r
+ <xsl:variable name="fileCount" select="count(file)"/>\r
+ <xsl:variable name="errorCount" select="count(file/error)"/>\r
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">\r
+ <tr>\r
+ <th>Files</th>\r
+ <th>Errors</th>\r
+ </tr>\r
+ <tr>\r
+ <xsl:call-template name="alternated-row"/>\r
+ <td><xsl:value-of select="$fileCount"/></td>\r
+ <td><xsl:value-of select="$errorCount"/></td>\r
+ </tr>\r
+ </table>\r
+ </xsl:template>\r
+ \r
+ <xsl:template name="alternated-row">\r
+ <xsl:attribute name="class">\r
+ <xsl:if test="position() mod 2 = 1">a</xsl:if>\r
+ <xsl:if test="position() mod 2 = 0">b</xsl:if>\r
+ </xsl:attribute> \r
+ </xsl:template> \r
+</xsl:stylesheet>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0"?>\r
+\r
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\r
+\r
+<xsl:template match="/">\r
+ <html>\r
+ <head>\r
+ <title>Sun Coding Style Violations</title>\r
+ </head>\r
+ <body bgcolor="#FFFFEF">\r
+ <p><b>Coding Style Check Results</b></p>\r
+ <table border="1" cellspacing="0" cellpadding="2">\r
+ <tr bgcolor="#CC9966">\r
+ <th colspan="2"><b>Summary</b></th>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total files checked</td>\r
+ <td><xsl:number level="any" value="count(descendant::file)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Files with errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::file[error])"/></td>\r
+ </tr>\r
+ <tr bgcolor="#CCF3D0">\r
+ <td>Total errors</td>\r
+ <td><xsl:number level="any" value="count(descendant::error)"/></td>\r
+ </tr>\r
+ <tr bgcolor="#F3F3E1">\r
+ <td>Errors per file</td>\r
+ <td><xsl:number level="any" value="count(descendant::error) div count(descendant::file)"/></td>\r
+ </tr>\r
+ </table>\r
+ <hr align="left" width="95%" size="1"/>\r
+ <p>The following are violations of the Sun Coding-Style Standards:</p>\r
+ <p/>\r
+ <xsl:apply-templates/>\r
+ </body>\r
+ </html>\r
+</xsl:template>\r
+\r
+<xsl:template match="file[error]">\r
+ <table bgcolor="#AFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> File: </th>\r
+ <td>\r
+ <xsl:value-of select="@name"/>\r
+ </td>\r
+ </tr>\r
+ </table>\r
+ <table bgcolor="#DFFFFF" width="95%" border="1" cellspacing="0" cellpadding="2">\r
+ <tr>\r
+ <th> Line Number </th>\r
+ <th> Error Message </th>\r
+ </tr>\r
+ <xsl:apply-templates select="error"/>\r
+ </table>\r
+ <p/>\r
+</xsl:template>\r
+\r
+<xsl:template match="error">\r
+ <tr>\r
+ <td>\r
+ <xsl:value-of select="@line"/>\r
+ </td>\r
+ <td>\r
+ <xsl:value-of select="@message"/>\r
+ </td>\r
+ </tr>\r
+</xsl:template>\r
+\r
+</xsl:stylesheet>\r
--- /dev/null
+<?xml version="1.0"?>\r
+<!DOCTYPE module PUBLIC\r
+ "-//Puppy Crawl//DTD Check Configuration 1.2//EN"\r
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">\r
+\r
+<!--\r
+\r
+ Checkstyle configuration that checks the sun coding conventions from:\r
+\r
+ - the Java Language Specification at\r
+ http://java.sun.com/docs/books/jls/second_edition/html/index.html\r
+\r
+ - the Sun Code Conventions at http://java.sun.com/docs/codeconv/\r
+\r
+ - the Javadoc guidelines at\r
+ http://java.sun.com/j2se/javadoc/writingdoccomments/index.html\r
+\r
+ - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html\r
+\r
+ - some best practices\r
+\r
+ Checkstyle is very configurable. Be sure to read the documentation at\r
+ http://checkstyle.sf.net (or in your downloaded distribution).\r
+\r
+ Most Checks are configurable, be sure to consult the documentation.\r
+\r
+ To completely disable a check, just comment it out or delete it from the file.\r
+\r
+ Finally, it is worth reading the documentation.\r
+\r
+-->\r
+\r
+<module name="Checker">\r
+\r
+ <!-- Checks that a package.html file exists for each package. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->\r
+ <!-- module name="PackageHtml"/ -->\r
+\r
+ <!-- Checks whether files end with a new line. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->\r
+ <module name="NewlineAtEndOfFile"/>\r
+\r
+ <!-- Checks that property files contain the same keys. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->\r
+ <module name="Translation"/>\r
+\r
+\r
+ <module name="TreeWalker">\r
+\r
+ <!-- Checks for Javadoc comments. -->\r
+ <!-- See http://checkstyle.sf.net/config_javadoc.html -->\r
+ <!-- module name="JavadocMethod"/ -->\r
+ <!-- module name="JavadocType"/ -->\r
+ <!-- module name="JavadocVariable"/ -->\r
+ <!-- module name="JavadocStyle"/ -->\r
+ \r
+ <!-- Checks for Naming Conventions. -->\r
+ <!-- See http://checkstyle.sf.net/config_naming.html -->\r
+ <module name="ConstantName"/>\r
+ <module name="LocalFinalVariableName"/>\r
+ <module name="LocalVariableName"/>\r
+ <module name="MemberName">
+ <property name="format" value="_[a-z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="MethodName" />\r
+ <module name="PackageName"/>\r
+ <module name="ParameterName">
+ <property name="format" value="^a[A-Z][a-zA-Z0-9]*$"/>
+ </module>\r
+ <module name="StaticVariableName">
+ <property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
+ </module>\r
+ <module name="TypeName"/>\r
+\r
+\r
+ <!-- Checks for Headers -->\r
+ <!-- See http://checkstyle.sf.net/config_header.html -->\r
+ <!-- <module name="Header"> -->\r
+ <!-- The follow property value demonstrates the ability -->\r
+ <!-- to have access to ANT properties. In this case it uses -->\r
+ <!-- the ${basedir} property to allow Checkstyle to be run -->\r
+ <!-- from any directory within a project. See property -->\r
+ <!-- expansion, -->\r
+ <!-- http://checkstyle.sf.net/config.html#properties -->\r
+ <!-- <property -->\r
+ <!-- name="headerFile" -->\r
+ <!-- value="${basedir}/java.header"/> -->\r
+ <!-- </module> -->\r
+\r
+ <!-- Following interprets the header file as regular expressions. -->\r
+ <!-- <module name="RegexpHeader"/> -->\r
+\r
+\r
+ <!-- Checks for imports -->\r
+ <!-- See http://checkstyle.sf.net/config_import.html -->\r
+ <module name="AvoidStarImport"/>\r
+ <module name="IllegalImport"/> <!-- defaults to sun.* packages -->\r
+ <module name="RedundantImport"/>\r
+ <module name="UnusedImports"/>\r
+\r
+\r
+ <!-- Checks for Size Violations. -->\r
+ <!-- See http://checkstyle.sf.net/config_sizes.html -->\r
+ <module name="FileLength"/>\r
+ <module name="LineLength">
+ <property name="max" value="120"/>
+ </module>\r
+ <module name="MethodLength"/>\r
+ <module name="ParameterNumber"/>\r
+\r
+\r
+ <!-- Checks for whitespace -->\r
+ <!-- See http://checkstyle.sf.net/config_whitespace.html -->\r
+ <module name="EmptyForIteratorPad"/>\r
+ <!-- module name="MethodParamPad"/ -->\r
+ <!-- module name="NoWhitespaceAfter"/ -->\r
+ <module name="NoWhitespaceBefore"/>\r
+ <module name="OperatorWrap"/>\r
+ <module name="ParenPad"/>\r
+ <!-- module name="TypecastParenPad"/ -->\r
+ <module name="TabCharacter"/>\r
+ <module name="WhitespaceAfter"/>\r
+ <!-- module name="WhitespaceAround">
+ <property name="tokens" value=" ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR,MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,GENERIC_START,GENERIC_END,TYPE_EXTENSION_AND,WILDCARD_TYPE"/>
+ </module -->\r
+\r
+\r
+ <!-- Modifier Checks -->\r
+ <!-- See http://checkstyle.sf.net/config_modifiers.html -->\r
+ <module name="ModifierOrder"/>\r
+ <module name="RedundantModifier"/>\r
+\r
+\r
+ <!-- Checks for blocks. You know, those {}'s -->\r
+ <!-- See http://checkstyle.sf.net/config_blocks.html -->\r
+ <module name="AvoidNestedBlocks">
+ <property name="allowInSwitchCase" value="true"/>
+ </module>\r
+ <module name="EmptyBlock"/>\r
+ <module name="LeftCurly"/>\r
+ <module name="NeedBraces"/>\r
+ <module name="RightCurly"/>\r
+\r
+\r
+ <!-- Checks for common coding problems -->\r
+ <!-- See http://checkstyle.sf.net/config_coding.html -->\r
+ <module name="AvoidInlineConditionals"/>\r
+ <!-- module name="DoubleCheckedLocking"/ --> \r
+ <module name="EmptyStatement"/>\r
+ <module name="EqualsHashCode"/>\r
+ <module name="HiddenField"/>\r
+ <module name="IllegalInstantiation"/>\r
+ <module name="InnerAssignment"/>\r
+ <module name="MagicNumber">
+ <property name="ignoreNumbers" value="-1,0,1,2,3,4"/>
+ </module>\r
+ <module name="MissingSwitchDefault"/>\r
+ <module name="RedundantThrows"/>\r
+ <module name="SimplifyBooleanExpression"/>\r
+ <module name="SimplifyBooleanReturn"/>\r
+ <!-- module name="ExplicitInitialization"/ -->\r
+\r
+ <!-- Checks for class design -->\r
+ <!-- See http://checkstyle.sf.net/config_design.html -->\r
+ <!-- module name="DesignForExtension"/ -->\r
+ <module name="FinalClass"/>\r
+ <module name="HideUtilityClassConstructor"/>\r
+ <module name="InterfaceIsType"/>\r
+ <module name="VisibilityModifier"/>\r
+\r
+\r
+ <!-- Miscellaneous other checks. -->\r
+ <!-- See http://checkstyle.sf.net/config_misc.html -->\r
+ <module name="ArrayTypeStyle"/>\r
+ <!-- module name="FinalParameters"/ -->\r
+ <!-- module name="GenericIllegalRegexp">\r
+ <property name="format" value="\s+$"/>\r
+ <property name="message" value="Line has trailing spaces."/>\r
+ </module -->\r
+ <module name="TodoComment"/>\r
+ <module name="UpperEll"/>\r
+\r
+ </module>\r
+\r
+</module>\r
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-hibernate-jpa</artifactId>
+ <packaging>jar</packaging>
+ <name>/hibernate-jpa</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <!-- This dependency should probably move to a more generic JPA
+ project -->
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>transaction-api</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <version>3.4.0.GA</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.transaction</groupId>
+ <artifactId>jta</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ </dependencies>
+
+ <!-- repositories>
+ <repository>
+ <id>jboss</id>
+ <name>JBoss Repo</name>
+ <url>http://repository.jboss.org/maven2</url>
+ </repository>
+ </repositories-->
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <packaging>pom</packaging>
+ <version>0.2</version>
+ <name>/</name>
+ <description>wamblee.org utilities library</description>
+ <url>http://wamblee.org</url>
+ <licenses>
+ <license>
+ <name>The Apache Software License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <url>https://wamblee.org/viewvc/utils/tags/wamblee-utils-0.2</url>
+ <connection>scm:svn:https://wamblee.org/svn/public/utils/tags/wamblee-utils-0.2</connection>
+ </scm>
+
+ <modules>
+ <module>support</module>
+ <module>system</module>
+ <module>hibernate-jpa</module>
+ <module>security</module>
+ <module>test</module>
+ </modules>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbyclient</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbynet</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.8.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>5.7</version>
+ <scope>test</scope>
+ <classifier>jdk15</classifier>
+ </dependency -->
+ </dependencies>
+
+ <dependencyManagement>
+ <dependencies>
+
+ <dependency>
+ <groupId>oro</groupId>
+ <artifactId>oro</artifactId>
+ <version>2.0.6</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>transaction-api</artifactId>
+ <version>1.1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.4</version>
+ </dependency>
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ <version>2.4.7</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.5.11</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ <version>1.5.11</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ <version>1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.activation</groupId>
+ <artifactId>activation</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.mail</groupId>
+ <artifactId>mail</artifactId>
+ <version>1.4.1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ <type>jar</type>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>5.1.12</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ <version>10.5.3.0_1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbyclient</artifactId>
+ <version>10.5.3.0_1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbynet</artifactId>
+ <version>10.5.3.0_1</version>
+ </dependency>
+ <dependency>
+ <groupId>quartz</groupId>
+ <artifactId>quartz</artifactId>
+ <version>1.5.1</version>
+ </dependency>
+ <dependency>
+ <groupId>jtidy</groupId>
+ <artifactId>jtidy</artifactId>
+ <version>4aug2000r7-dev</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-httpclient</groupId>
+ <artifactId>commons-httpclient</artifactId>
+ <version>3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ <version>1.7.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-jms</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-hibernate3</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-jpa</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aop</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+
+ <!-- should be possible to remove the dependence on log4j -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ <version>1.6</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ <version>1.2.3</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-email</groupId>
+ <artifactId>commons-email</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ <version>1.1-beta-9</version>
+ <exclusions>
+ <exclusion>
+ <groupId>xom</groupId>
+ <artifactId>xom</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>xerces</groupId>
+ <artifactId>xmlParserAPIs</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>jstl</groupId>
+ <artifactId>jstl</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>taglibs</groupId>
+ <artifactId>standard</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jfreechart</artifactId>
+ <version>1.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jcommon</artifactId>
+ <version>1.0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax</groupId>
+ <artifactId>javaee-api</artifactId>
+ <version>6.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.3</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-dbcp</groupId>
+ <artifactId>commons-dbcp</artifactId>
+ <version>1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>toplink.essentials</groupId>
+ <artifactId>toplink-essentials</artifactId>
+ <version>2.0-36</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>eclipselink</artifactId>
+ <version>2.0.0</version>
+ <scope>compile</scope>
+ </dependency>
+
+
+
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.0</version>
+ <configuration>
+ <autoVersionSubmodules>true</autoVersionSubmodules>
+ <goals>javadoc:jar deploy</goals>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>2.4</version>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>.</directory>
+ <includes>
+ <include>derby.log</include>
+ <include>createDDL.jdbc</include>
+ <include>dropDDL.jdbc</include>
+ </includes>
+ <excludes>
+ <exclude>**/important.log</exclude>
+ <exclude>**/another-important.log</exclude>
+ </excludes>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>jalopy-maven-plugin</artifactId>
+ <configuration>
+ <fileFormat>UNIX</fileFormat>
+ <convention>org.wamblee.jalopy.xml</convention>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ </configuration>
+ </plugin>
+
+ <!-- Make sure other projects can use (or the test support
+ and test classes from the projects it uses. To use
+ a dependence on a test library of a project, an additinoal
+ dependence must be added with <type>test-jar</type>
+ -->
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>clean</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instrumentation>
+ <excludes> </excludes>
+ </instrumentation>
+ </configuration>
+ </plugin>
+
+
+ </plugins>
+
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-project-info-reports-plugin</artifactId>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>checkstyle</report>
+ <report>javadoc</report>
+ <report>dependencies</report>
+ <report>project-team</report>
+ <report>mailing-list</report>
+ <report>issue-tracking</report>
+ <report>license</report>
+ <report>scm</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>changes-maven-plugin</artifactId>
+ <version>2.0-beta-1</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>surefire-report-maven-plugin</artifactId>
+ </plugin>
+ <!-- Test coverage reporting -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <configuration>
+ <formats>
+ <format>xml</format>
+ <format>html</format>
+ </formats>
+ </configuration>
+ </plugin>
+
+ <!-- checkstyle -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <configLocation>config/sun_checks.xml</configLocation>
+ </configuration>
+ </plugin>
+
+ <!-- taglist -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>taglist-maven-plugin</artifactId>
+ <configuration>
+ <tags>
+ <tag>TODO</tag>
+ <tag>@todo</tag>
+ <tag>FIXME</tag>
+ </tags>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </reporting>
+
+ <profiles>
+ <profile>
+ <id>release</id>
+ <activation>
+ <property>
+ <name>performRelease</name>
+ <value>true</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>all</id>
+ <activation>
+ <property>
+ <name>!performRelease</name>
+ </property>
+ </activation>
+ <distributionManagement>
+ <repository>
+ <id>local</id>
+ <name>Local directory</name>
+ <url>file:${user.home}/java/workspace/deploy</url>
+ </repository>
+ </distributionManagement>
+ </profile>
+ </profiles>
+
+
+ <properties>
+ <spring.version>2.0.8</spring.version>
+ <jmock.version>2.4.0</jmock.version>
+ </properties>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-security</artifactId>
+ <packaging>jar</packaging>
+ <name>/security</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <type>test-jar</type>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system-spring</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system-spring</artifactId>
+ <type>test-jar</type>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system-general</artifactId>
+ <type>test-jar</type>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-spring</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-spring</artifactId>
+ <type>test-jar</type>
+ <version>0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-hibernate-jpa</artifactId>
+ <version>0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-hibernate3</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aop</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>transaction-api</artifactId>
+ <version>1.1</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * An superclass of all other operations.
+ */
+public class AllOperation implements Operation {
+
+ private static final String OPERATION = "all";
+
+ /**
+ * Operation name.
+ */
+ private String _name;
+
+ /**
+ * Constructs an all operation.
+ *
+ */
+ public AllOperation() {
+ _name = OPERATION;
+ }
+
+ /**
+ * Constructs the operation, this constructor is the one that must be used
+ * by subclasses.
+ * @param aName Name of the operation. This name must be unique among all operations.
+ */
+ protected AllOperation(String aName) {
+ _name = aName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.Operation#getName()
+ */
+ public String getName() {
+ return _name;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Matches any user.
+ *
+ * @author Erik Brakkee
+ */
+public class AnyUserCondition extends AbstractPersistent implements UserCondition {
+
+ /**
+ * Constructs the condition.
+ *
+ */
+ public AnyUserCondition() {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.UserCondition#matches(org.wamblee.usermgt.User)
+ */
+ public boolean matches(User aUser) {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "AnyUserCondition()";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Authorization exception to be thrown when
+ * a resouce may not be accessed.
+ *
+ * @author Erik Brakkee
+ */
+public class AuthorizationException extends RuntimeException {
+
+ private Object _resource;
+ private Operation _operation;
+
+ public AuthorizationException(Object aResource, Operation aOperation) {
+ super("Operation '" + aOperation + "' not allowed on '" + aResource + "'");
+ _resource = aResource;
+ _operation = aOperation;
+ }
+
+ public Object getResource() {
+ return _resource;
+ }
+
+ public Operation getOperation() {
+ return _operation;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Inititializer class for authorization rules. This class initializes the
+ * authorization rules in case none are present.
+ */
+public class AuthorizationInitializer {
+
+ private static final Logger LOGGER = Logger.getLogger(AuthorizationInitializer.class);
+
+ /**
+ * Initializes authorization rules in case none are present.
+ * @param aService Authorization service.
+ * @param aRules Default rules for initialization.
+ */
+ public AuthorizationInitializer(AuthorizationService aService, AuthorizationRule[] aRules) {
+ if ( aService.getRules().length == 0 ) {
+ for (AuthorizationRule rule: aRules) {
+ LOGGER.info("Appending authorization rule " + rule);
+ aService.appendRule(rule);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Represents the result of an authorization decision.
+ *
+ * @author Erik Brakkee
+ */
+public enum AuthorizationResult {
+ /**
+ * Access is granted.
+ */
+ GRANTED,
+
+ /**
+ * Access is denied.
+ */
+ DENIED,
+
+ /**
+ * Access is undecided.
+ */
+ UNDECIDED,
+
+ /**
+ * Unsupported resource.
+ */
+ UNSUPPORTED_RESOURCE
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Represents an authorization rule to determine whether an operation is allowed on a resource.
+ *
+ * @author Erik Brakkee
+ */
+public interface AuthorizationRule extends Persistent {
+
+ /**
+ * Returns the supported object types for which this authorization rule applies.
+ * This can be used by the authorization service for optimization.
+ * @return Array of supported types.
+ */
+ Class[] getSupportedTypes();
+
+ /**
+ * Determines whether an operation is allowed on a certain resource.
+ * The rule implementation must be prepared to deal with resources for which it does
+ * not apply. In those cases it should return {@link AuthorizationResult#UNSUPPORTED_RESOURCE}.
+ * @param aResource Resource.
+ * @param anOperation Operation.
+ * @param aUser Current user.
+ * @return Authorization result.
+ */
+ AuthorizationResult isAllowed(Object aResource, Operation anOperation, User aUser);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Service to determine if access to a certain resource is allowed.
+ *
+ * @author Erik Brakkee
+ */
+public interface AuthorizationService extends Persistent {
+
+ /**
+ * Checks whether an operation is allowed on a resource.
+ * @param aResource Resource.
+ * @param aOperation Operation.
+ * @return Checks whether the operation is allowed on a resource.
+ */
+ boolean isAllowed(Object aResource, Operation aOperation);
+
+ /**
+ * Same as {@link #isAllowed(Object, Operation)} but throws a
+ * <code>RuntimeException</code> in case access is not allowed.
+ * @param aResource Resource to check.
+ * @param aOperation Operation to perform.
+ * @return Resource that was checked.
+ */
+ <T> T check(T aResource, Operation aOperation);
+
+ /**
+ * Gets the authorization rules.
+ * @return Rules.
+ */
+ AuthorizationRule[] getRules();
+
+ /**
+ * Appends a new authorization rule to the end.
+ * @param aRule Rule to append.
+ */
+ void appendRule(AuthorizationRule aRule);
+
+ /**
+ * Removes a rule.
+ * @param aRule Index of the rule to remove.
+ */
+ void removeRule(int aIndex);
+
+ /**
+ * Inserts a rule.
+ * @param aIndex Index of the position of the rule after insertion.
+ * @param aRule Rule to insert.
+ */
+ void insertRuleAfter(int aIndex, AuthorizationRule aRule);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Represents an operation to create something.
+ *
+ * @author Erik Brakkee
+ */
+public class CreateOperation extends AllOperation {
+
+ private static final String OPERATION = "create";
+
+ /**
+ * Constructs the operation.
+ *
+ */
+ public CreateOperation() {
+ super(OPERATION);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+import org.wamblee.usermgt.UserAccessor;
+
+/**
+ * Default implementation of an authorization service.
+ * To determine whether access to a resource is allowed, the service consults a number
+ * of authorization rules in a fixed order. The first rule that gives a result GRANTED or
+ * DENIED determines the result of the evaluation. Rules that return any other result are
+ * ignoed. If none of the rules match, than access is denied.
+ *
+ * @author Erik Brakkee
+ */
+public class DefaultAuthorizationService extends AbstractPersistent implements AuthorizationService {
+
+ /**
+ * List of ordered authorization rules.
+ */
+ private List<AuthorizationRule> _rules;
+
+ /**
+ * User accessor used to obtain the current user.
+ */
+ private UserAccessor _userAccessor;
+
+ /**
+ * Name for this instance of the authorization service.
+ */
+ private String _name;
+
+ /**
+ * Constructs the service.
+ * @param aAccessor User accessor.
+ * @param aName Name of this instance of the service.
+ */
+ public DefaultAuthorizationService(UserAccessor aAccessor, String aName) {
+ _rules = new ArrayList<AuthorizationRule>();
+ _userAccessor = aAccessor;
+ _name = aName;
+ }
+
+ /**
+ * Constructs the authorization service.
+ */
+ public DefaultAuthorizationService() {
+ _rules = new ArrayList<AuthorizationRule>();
+ _userAccessor = null;
+ _name = null;
+ }
+
+ /**
+ * Sets the user accessor.
+ * @param aUserAccessor User accessor.
+ */
+ public void setUserAccessor(UserAccessor aUserAccessor) {
+ _userAccessor = aUserAccessor;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#isAllowed(java.lang.Object, org.wamblee.security.authorization.Operation)
+ */
+ public boolean isAllowed(Object aResource, Operation aOperation) {
+ User user = _userAccessor.getCurrentUser();
+ for (AuthorizationRule rule: _rules) {
+ switch ( rule.isAllowed(aResource, aOperation, user)) {
+ case DENIED: { return false; }
+ case GRANTED: { return true; }
+ }
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#check(T, org.wamblee.security.authorization.Operation)
+ */
+ public <T> T check(T aResource, Operation aOperation) {
+ if ( !isAllowed(aResource, aOperation)) {
+ throw new AuthorizationException(aResource, aOperation);
+ }
+ return aResource;
+ }
+
+ protected String getName() {
+ return _name;
+ }
+
+ public void setName(String aName) {
+ _name = aName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#getRules()
+ */
+ public AuthorizationRule[] getRules() {
+ return _rules.toArray(new AuthorizationRule[0]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#appendRule(org.wamblee.security.authorization.AuthorizationRule)
+ */
+ public void appendRule(AuthorizationRule aRule) {
+ _rules.add(aRule);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#insertRuleAfter(int, org.wamblee.security.authorization.AuthorizationRule)
+ */
+ public void insertRuleAfter(int aIndex, AuthorizationRule aRule) {
+ _rules.add(aIndex, aRule);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#removeRule(int)
+ */
+ public void removeRule(int aIndex) {
+ _rules.remove(aIndex);
+ }
+
+ /**
+ * For OR mapping.
+ * @return The rules.
+ */
+ protected List<AuthorizationRule> getMappedRules() {
+ return _rules;
+ }
+
+ /**
+ * For OR mapping.
+ * @param aRules The rules.
+ */
+ protected void setMappedRules(List<AuthorizationRule> aRules) {
+ _rules = aRules;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Operation registry implementation.
+ * This implementation ignores the distinction between different types of resources
+ * and simply assumes that every operation is applicable to every type of resource.
+ *
+ * @author Erik Brakkee
+ */
+public class DefaultOperationRegistry implements OperationRegistry {
+
+ private Map<String,Operation> _operations;
+
+ public DefaultOperationRegistry(Operation[] aOperations) {
+ _operations = new TreeMap<String, Operation>();
+ for (Operation operation: aOperations) {
+ _operations.put(operation.getName(), operation);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.OperationRegistry#getOperations(java.lang.Class)
+ */
+ public Operation[] getOperations(Class aResourceClass) {
+ return _operations.values().toArray(new Operation[0]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.OperationRegistry#encode(org.wamblee.security.authorization.Operation[])
+ */
+ public String encode(Operation[] aOperations) {
+ StringBuffer buffer = new StringBuffer();
+ for (Operation operation: aOperations) {
+ if ( buffer.length() > 0 ) {
+ buffer.append(',');
+ }
+ buffer.append(operation.getName());
+ }
+ return buffer.toString();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.OperationRegistry#decode(java.lang.Class, java.lang.String)
+ */
+ public Operation[] decode(Class aResourceClass, String aOperationsString) {
+ return decode(aOperationsString);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.OperationRegistry#decode(java.lang.String)
+ */
+ public Operation[] decode(String aOperationsString) {
+ if ( aOperationsString.length() == 0 ) {
+ return new Operation[0];
+ }
+ String[] names = aOperationsString.split(",");
+ ArrayList<Operation> result = new ArrayList<Operation>();
+ for (String name: names) {
+ Operation operation = _operations.get(name);
+ if ( operation == null ) {
+ throw new IllegalArgumentException("Unknown operation '" + name + "'");
+ }
+ result.add(operation);
+ }
+ return result.toArray(new Operation[0]);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Deletes the operation.
+ *
+ * @author Erik Brakkee
+ */
+public class DeleteOperation extends AllOperation {
+
+ private static final String OPERATION = "delete";
+
+ /**
+ * Constructs the operation.
+ *
+ */
+ public DeleteOperation() {
+ super(OPERATION);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Checks if a user against a specific group.
+ *
+ * @author Erik Brakkee
+ */
+public class GroupUserCondition extends AbstractPersistent implements UserCondition {
+
+ /**
+ * Group the user must be in.
+ */
+ private String _group;
+
+ /**
+ * Constructs the condition.
+ * @param aGroup Group the user must be in.
+ */
+ public GroupUserCondition(String aGroup) {
+ _group = aGroup;
+ }
+
+ /**
+ * For OR mapping.
+ *
+ */
+ protected GroupUserCondition() {
+ _group = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.UserCondition#matches(org.wamblee.usermgt.UserAccessor)
+ */
+ public boolean matches(User aUser) {
+ return aUser.isInGroup(_group);
+ }
+
+ /**
+ * @return Returns the _group.
+ */
+ protected String getGroup() {
+ return _group;
+ }
+
+ /**
+ * @param _group The _group to set.
+ */
+ protected void setGroup(String aGroup) {
+ _group = aGroup;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "GroupUserCondition(group=" + _group + ")";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+
+/**
+ * Determiens if an operation is a subclass of a specified operation.
+ */
+public class IsaOperationCondition extends AbstractPersistent implements
+ OperationCondition {
+
+ /**
+ * Operation that the other operation must be a subclass of.
+ */
+ private Class<? extends Operation> _operation;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aOperation
+ * Operation that an operation must be an instance of.
+ */
+ public IsaOperationCondition(Class<? extends Operation> aOperation) {
+ _operation = aOperation;
+ }
+
+ /**
+ * For OR mapping.
+ *
+ */
+ public IsaOperationCondition() {
+ _operation = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.OperationCondition#matches(org.wamblee.security.authorization.Operation)
+ */
+ public boolean matches(Operation aOperation) {
+ return _operation.isInstance(aOperation);
+ }
+
+ /**
+ * Gets the operation as a string. For OR mapping only.
+ *
+ * @return Operation string.
+ */
+ protected String getOperationString() {
+ if (_operation == null) {
+ return null;
+ }
+ return _operation.getName();
+ }
+
+ /**
+ * Sets the operation as a string. For OR mapping only.
+ *
+ * @param aOperation
+ * Operation string.
+ */
+ protected void setOperationString(String aOperation) {
+ if (aOperation == null ) {
+ return;
+ }
+ try {
+ _operation = (Class<? extends Operation>)Class.forName(aOperation);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unknown class '" + aOperation + "'");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "IsaOperationCondition(operation=" + _operation.getName() + ")";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Represents an operation on a resource.
+ * An operation should contain no state to be persisted since only the name of the
+ * operation is persisted.
+ *
+ * @author Erik Brakkee
+ */
+public interface Operation {
+
+ /**
+ * Gets the name of the operation.
+ * @return Operation.
+ */
+ String getName();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Checks if an operation matches a condition.
+ *
+ * @author Erik Brakkee
+ */
+public interface OperationCondition extends Persistent {
+
+
+ /**
+ * Determines if the operation matches.
+ * @param aOperation Operation.
+ * @return True iff the operation matches.
+ */
+ boolean matches(Operation aOperation);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+
+/**
+ * Utility to map between a list of operations and a string based
+ * on the names of the operations.
+ *
+ * @author Erik Brakkee
+ */
+public interface OperationRegistry {
+
+ /**
+ * Gets the supported operations for a given resource class.
+ * @param aResourceClass Resource class.
+ * @return Supported operations for that class.
+ */
+ Operation[] getOperations(Class aResourceClass);
+
+ /**
+ * Converts a number of operations to a string.
+ * @param aOperations Operations to convert.
+ * @return String representation of the allowed operations.
+ */
+ String encode(Operation[] aOperations);
+
+ /**
+ * Converts an operations string to an array of operations.
+ * @param aResourceClass Resource class.
+ * @param aOperationsString Operations string as returned by {@link #encode(Operation[])}.
+ * @return Operations array.
+ */
+ Operation[] decode(Class aResourceClass, String aOperationsString);
+
+ /**
+ * Converts an operations string to an array of operations.
+ * @param aOperationsString Operations string as returned by {@link #encode(Operation[])}.
+ * @return Operations array.
+ */
+ Operation[] decode(String aOperationsString);
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Checks if a path satisfies a condition.
+ *
+ * @author Erik Brakkee
+ */
+public interface PathCondition extends Persistent {
+
+ /**
+ * Checks if the path matches the condition.
+ * @param aPath Path to match.
+ * @return True iff the path matches.
+ */
+ boolean matches(String aPath);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Represents a read operation on a resource.
+ *
+ * @author Erik Brakkee
+ */
+public class ReadOperation extends AllOperation {
+
+ private static final String OPERATION = "read";
+
+ /**
+ * Constructs the operation.
+ *
+ */
+ public ReadOperation() {
+ super(OPERATION);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.AbstractPersistent;
+
+/**
+ * Condition to check whether a path matches a given regula expression.
+ *
+ * @author Erik Brakkee
+ */
+public class RegexpPathCondition extends AbstractPersistent implements PathCondition {
+
+ /**
+ * String the path must start with.
+ */
+ private String _pattern;
+
+ /**
+ * Constructs the condition.
+ * @param aPattern String the path must start with.
+ */
+ public RegexpPathCondition(String aPattern) {
+ _pattern = aPattern;
+ }
+
+ /**
+ * For OR mapping.
+ *
+ */
+ protected RegexpPathCondition() {
+ _pattern = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.PathCondition#matches(java.lang.String)
+ */
+ public boolean matches(String aPath) {
+ return aPath.matches(_pattern);
+ }
+
+ /**
+ * @return Returns the _path.
+ */
+ protected String getPattern() {
+ return _pattern;
+ }
+
+ /**
+ * @param aPattern The _path to set.
+ */
+ protected void setPattern(String aPattern) {
+ _pattern = aPattern;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "RegexpCondition(pattern = '" + _pattern + "')";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+
+/**
+ * Condition to check whether a path starts with a given string.
+ *
+ * @author Erik Brakkee
+ */
+public class StartsWithPathCondition extends RegexpPathCondition {
+
+ /**
+ * Constructs the condition.
+ * @param aPath String the path must start with.
+ */
+ public StartsWithPathCondition(String aPath) {
+ super( aPath + ".*");
+ }
+
+ /**
+ * For OR mapping.
+ *
+ */
+ protected StartsWithPathCondition() {
+ super();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "StartsWithPathCondition(pattern = '" + getPattern() + "')";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.DENIED;
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNDECIDED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNSUPPORTED_RESOURCE;
+
+import org.apache.log4j.Logger;
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Utility base class for implementation of authentication rules based on the
+ * <ul>
+ * <li> The path of the resource. To obtain the path of a resource, subclasses
+ * must implement {@link #getResourcePath(Object)}.
+ * Whether a path is appropriate is determined by a
+ * {@link org.wamblee.security.authorization.PathCondition}.
+ * </li>
+ * <li> The user identity with which the resource is accessed.
+ * Whether a user is appropriate is determined by
+ * a {@link org.wamblee.security.authorization.UserCondition}.
+ * </li>
+ * <li> The operation that is requested.
+ * Whether the operation is appropriate is determined by a
+ * {@link org.wamblee.security.authorization.OperationCondition}.
+ * </li>
+ * </ul>
+ *
+ * In case all three conditions match, the condition returns the configured
+ * result passed at construction (GRANTED or DENIED). If the resource is not
+ * of the specified type, the result is UNSUPPORTED_RESOURCE, otherwise, the
+ * result is UNDECIDED.
+ */
+public abstract class UrlAuthorizationRule extends AbstractPersistent implements AuthorizationRule {
+
+ private static final Logger LOGGER = Logger.getLogger(UrlAuthorizationRule.class);
+
+ /**
+ * Result that the rule will return in case there is a match.
+ */
+ private AuthorizationResult _result;
+
+ /**
+ * A condition which specifies which users the rule is for.
+ */
+ private UserCondition _userCondition;
+
+ /**
+ * Path the rule applies for.
+ */
+ private PathCondition _pathCondition;
+
+ /**
+ * Resource class that the rule applies for.
+ */
+ private Class _resourceClass;
+
+ /**
+ * Operation that this rule is for.
+ */
+ private OperationCondition _operationCondition;
+
+ /**
+ * Constructs an authorization rule.
+ * IF the group and path match, then the provided result will be returned.
+ * @param aResult Result of the authorization when the path and group match.
+ * @param aUserCondition Condition to match users.
+ * @param aPathCondition Condition to match paths with.
+ * @param aResourceClass Supported resource class this is for.
+ * @param aOperationCondition Condition to match the operation with.
+ */
+ protected UrlAuthorizationRule(AuthorizationResult aResult, UserCondition aUserCondition,
+ PathCondition aPathCondition, Class aResourceClass, OperationCondition aOperationCondition) {
+ if ( !aResult.equals(GRANTED) && !aResult.equals(DENIED)) {
+ throw new IllegalArgumentException("Only GRANTED or DENIED may be used: " + aResult);
+ }
+ _result = aResult;
+ _userCondition = aUserCondition;
+ _pathCondition = aPathCondition;
+ _resourceClass = aResourceClass;
+ _operationCondition = aOperationCondition;
+ }
+
+ /**
+ * For OR mapping.
+ *
+ */
+ protected UrlAuthorizationRule(Class aResourceClass) {
+ _result = null;
+ _userCondition = null;
+ _pathCondition = null;
+ _resourceClass = aResourceClass;
+ _operationCondition = null;
+ }
+
+ /**
+ * For OR mapping.
+ *
+ */
+ protected UrlAuthorizationRule() {
+ _result = null;
+ _userCondition = null;
+ _pathCondition = null;
+ _resourceClass = null;
+ _operationCondition = null;
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationRule#getSupportedTypes()
+ */
+ public Class[] getSupportedTypes() {
+ return new Class[] { _resourceClass };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationRule#isAllowed(java.lang.Object,
+ * org.wamblee.security.authorization.Operation)
+ */
+ public AuthorizationResult isAllowed(Object aResource, Operation anOperation, User aUser) {
+ if ( ! _resourceClass.isInstance(aResource)) {
+ return UNSUPPORTED_RESOURCE;
+ }
+ String path = getResourcePath(aResource);
+ return isAllowed(path, anOperation, aUser);
+ }
+
+ /**
+ * Determines if the operation is allowed on the resource.
+ * @param aPath Path of the resource.
+ * @param aOperation Operation to be done.
+ * @param aUser Currently logged in user or null if no user is logged in.
+ * @return Authorization result,
+ */
+ protected AuthorizationResult isAllowed(String aPath, Operation aOperation, User aUser) {
+ if ( ! _pathCondition.matches(aPath) ) {
+ return UNDECIDED;
+ }
+ if ( !_operationCondition.matches(aOperation) ) {
+ return UNDECIDED;
+ }
+ if ( !_userCondition.matches(aUser)) {
+ return UNDECIDED;
+ }
+ return _result;
+ }
+
+ /**
+ * Gets the path of the resource.
+ * @param aResource Resource, guaranteed to be an instance of
+ * {@link #_resourceClass}.
+ * @return Path of the resource.
+ */
+ protected abstract String getResourcePath(Object aResource);
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "UrlAUthorizationRule(result = " + _result +
+ ", pathCondition = " + _pathCondition +
+ ", userCondition = " + _userCondition +
+ ", resourceClass = " + _resourceClass + ")";
+ }
+
+ /**
+ * Gets the authorization result for OR mapping.
+ * @return Result.
+ */
+ protected String getAuthorizationResultString() {
+ if ( _result == null ) {
+ return null;
+ }
+ return _result.toString();
+ }
+
+ /**
+ * Sets the authorization result, for OR mapping.
+ * @param aResult Result.
+ */
+ protected void setAuthorizationResultString(String aResult) {
+ _result = AuthorizationResult.valueOf(aResult);
+ }
+
+ protected String getResourceClassName() {
+ if ( _resourceClass == null ) {
+ return "";
+ }
+ return _resourceClass.getName();
+ }
+
+ protected void setResourceClassName(String aResourceClass) {
+ try {
+ _resourceClass = Class.forName(aResourceClass);
+ } catch (ClassNotFoundException e) {
+ LOGGER.error("Cannot find resource class '" + aResourceClass + "'", e);
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * @return Returns the _operationCondition.
+ */
+ public OperationCondition getOperationCondition() {
+ return _operationCondition;
+ }
+
+ /**
+ * @param aOperationCondition The _operationCondition to set.
+ */
+ protected void setOperationCondition(OperationCondition aOperationCondition) {
+ _operationCondition = aOperationCondition;
+ }
+
+ /**
+ * @return Returns the _pathCondition.
+ */
+ public PathCondition getPathCondition() {
+ return _pathCondition;
+ }
+
+ /**
+ * @param aPathCondition The _pathCondition to set.
+ */
+ protected void setPathCondition(PathCondition aPathCondition) {
+ _pathCondition = aPathCondition;
+ }
+
+ /**
+ * @return Returns the _userCondition.
+ */
+ public UserCondition getUserCondition() {
+ return _userCondition;
+ }
+
+ /**
+ * @param aUserCondition The _userCondition to set.
+ */
+ protected void setUserCondition(UserCondition aUserCondition) {
+ _userCondition = aUserCondition;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import org.wamblee.persistence.Persistent;
+import org.wamblee.usermgt.User;
+
+/**
+ * Condition used to match a user against a specified set of users.
+ *
+ * @author Erik Brakkee
+ */
+public interface UserCondition extends Persistent {
+
+ /**
+ * Determines if the condition matches.
+ * @param aUser user to check.
+ * @return True if the condition matches, false otherwise.
+ */
+ boolean matches(User aUser);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * Represents a write operation on a resource.
+ *
+ * @author Erik Brakkee
+ */
+public class WriteOperation extends AllOperation {
+
+ private static final String OPERATION = "write";
+
+ /**
+ * Constructs the operation.
+ *
+ */
+ public WriteOperation() {
+ super(OPERATION);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization.hibernate;
+
+import org.wamblee.usermgt.hibernate.UsermgtHibernateMappingFiles;
+
+/**
+ * Mapping files for authorization.
+ *
+ * @author Erik Brakkee
+ */
+public class AuthorizationMappingFiles extends UsermgtHibernateMappingFiles {
+
+ public AuthorizationMappingFiles() {
+ super(new String[]{ "hbm/AuthorizationRule.hbm.xml", "hbm/UserCondition.hbm.xml",
+ "hbm/AuthorizationService.hbm.xml", "hbm/OperationCondition.hbm.xml", "hbm/PathCondition.hbm.xml",
+ "hbm/TestAuthorizationRule.hbm.xml" });
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization.hibernate;
+
+import java.util.List;
+
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.persistence.hibernate.HibernateSupport;
+import org.wamblee.security.authorization.AuthorizationRule;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.security.authorization.DefaultAuthorizationService;
+import org.wamblee.security.authorization.Operation;
+import org.wamblee.usermgt.UserAccessor;
+
+/**
+ * Authorization service with persistent storage.
+ * This is a wrapper for {@link org.wamblee.security.authorization.DefaultAuthorizationService}
+ * which refreshes the state of the service at certain time intervals.
+ *
+ * @author Erik Brakkee
+ */
+public class PersistentAuthorizationService extends AbstractPersistent
+ implements AuthorizationService {
+
+ /**
+ * Name of query to find the service by name.
+ */
+ private static final String FIND_QUERY = "findAuthorizationServiceByName";
+
+ /**
+ * Name of the query parameter for the service name.
+ */
+ private static final String NAME_PARAM = "name";
+
+ /**
+ * Authorization service to use.
+ */
+ private DefaultAuthorizationService _service;
+
+ /**
+ * Hibernate template to use.
+ */
+ private HibernateTemplate _template;
+
+ /**
+ * User accessor.
+ */
+ private UserAccessor _userAccessor;
+
+ /**
+ * Name of the service.
+ */
+ private String _name;
+
+ /**
+ * Refresh interval in milliseconds.
+ */
+ private final long _refreshInterval;
+
+ /**
+ * Last refresh time.
+ */
+ private long _lastRefreshTime;
+
+ /**
+ * Constructs the persistent service.
+ *
+ * @param aName
+ * Name of the service.
+ * @param aTemplate
+ * Hibernate template for hibernate usage.
+ * @param aAccessor
+ * User accessor.
+ * @param aRefresh
+ * Whether or not to refresh the state of the service at the
+ * start of every operation.
+ */
+ public PersistentAuthorizationService(String aName,
+ HibernateTemplate aTemplate, UserAccessor aAccessor,
+ long aRefreshInterval) {
+ _template = aTemplate;
+ _refreshInterval = aRefreshInterval;
+ _lastRefreshTime = System.currentTimeMillis();
+ _userAccessor = aAccessor;
+ _name = aName;
+ }
+
+ /**
+ * Initialize service if needed.
+ */
+ private void initialize() {
+ if (_service == null) {
+ List<DefaultAuthorizationService> result = _template
+ .findByNamedQueryAndNamedParam(FIND_QUERY, NAME_PARAM,
+ _name);
+
+ if (result.size() > 1) {
+ throw new IllegalArgumentException(
+ "Returned more than one service for name '" + _name
+ + "' (" + result.size() + ")");
+ }
+
+ if (result.size() == 0) {
+ _service = new DefaultAuthorizationService(_userAccessor, _name);
+ _template.persist(_service);
+ } else {
+ _service = result.get(0);
+ _service.setUserAccessor(_userAccessor);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationService#isAllowed(java.lang.Object,
+ * org.wamblee.security.authorization.Operation)
+ */
+ public boolean isAllowed(Object aResource, Operation aOperation) {
+ initialize();
+ refresh();
+ return _service.isAllowed(aResource, aOperation);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.AuthorizationService#check(T, org.wamblee.security.authorization.Operation)
+ */
+ public <T> T check(T aResource, Operation aOperation) {
+ initialize();
+ refresh();
+ return _service.check(aResource, aOperation);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationService#getRules()
+ */
+ public AuthorizationRule[] getRules() {
+ initialize();
+ refresh();
+ return _service.getRules();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationService#appendRule(org.wamblee.security.authorization.AuthorizationRule)
+ */
+ public void appendRule(AuthorizationRule aRule) {
+ initialize();
+ refresh();
+ _service.appendRule(aRule);
+ save();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationService#removeRule(int)
+ */
+ public void removeRule(int aIndex) {
+ initialize();
+ refresh();
+ _service.removeRule(aIndex);
+ save();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.authorization.AuthorizationService#insertRuleAfter(int,
+ * org.wamblee.security.authorization.AuthorizationRule)
+ */
+ public void insertRuleAfter(int aIndex, AuthorizationRule aRule) {
+ initialize();
+ refresh();
+ _service.insertRuleAfter(aIndex, aRule);
+ save();
+ }
+
+ /**
+ * Refreshes the state of the service through hibernate.
+ *
+ */
+ private synchronized void refresh() {
+ long time = System.currentTimeMillis();
+ if ( time - _lastRefreshTime > _refreshInterval ) {
+ _template.refresh(_service);
+ _lastRefreshTime = time;
+ }
+ }
+
+ /**
+ * Saves any changes to the service state if necessary.
+ */
+ private void save() {
+ HibernateSupport.merge(_template, _service);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.encryption;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Hex;
+
+/**
+ * MD5 Hex encoder.
+ *
+ * @author Erik Brakkee
+ */
+public class Md5HexMessageDigester implements MessageDigester {
+
+ /**
+ * Constructs the message digester.
+ *
+ */
+ public Md5HexMessageDigester() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.security.MessageDigester#hash(java.lang.String)
+ */
+ public String hash(String aValue) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ byte[] result = digest.digest(aValue.getBytes());
+ char[] charResult = Hex.encodeHex(result);
+ return new String(charResult);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("MD5 not supported????");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.encryption;
+
+/**
+ * Utility class that encapsulates a message digest method.
+ */
+public interface MessageDigester {
+
+ /**
+ * Computes a message digest for a value and encodes it in some way.
+ * @param aValue Value to compute digest for.
+ * @return Encoded digest.
+ */
+ String hash(String aValue);
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import static org.wamblee.usermgt.UserMgtException.Reason.DUPLICATE_USER;
+
+import org.wamblee.security.encryption.MessageDigester;
+
+/**
+ * User set base class.
+ */
+public abstract class AbstractUserSet implements UserSet {
+
+ /**
+ * Password validator.
+ */
+ private NameValidator _passwordValidator;
+
+ /**
+ * Password encoder.
+ */
+ private MessageDigester _passwordEncoder;
+
+
+ protected AbstractUserSet(NameValidator aPasswordValidator,
+ MessageDigester aPasswordEncoder) {
+ _passwordValidator = aPasswordValidator;
+ _passwordEncoder = aPasswordEncoder;
+ }
+
+ /**
+ * Sets the password validtor and encoder in the user.
+ * @param aUser User.
+ */
+ protected void setPasswordInfo(User aUser) {
+ aUser.setPasswordValidator(_passwordValidator);
+ aUser.setPasswordEncoder(_passwordEncoder);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.UserSet#createUser(java.lang.String, java.lang.String, org.wamblee.usermgt.Group)
+ */
+ public User createUser(String aUsername, String aPassword, Group aGroup) throws UserMgtException {
+ User user = new User(aUsername, aPassword, aGroup, _passwordValidator, _passwordEncoder);
+ if (contains(user)) {
+ throw new UserMgtException(DUPLICATE_USER, user);
+ }
+ add(user);
+ return user;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.io.Serializable;
+
+import org.wamblee.persistence.AbstractPersistent;
+
+/**
+ * Represents a group.
+ *
+ * @author Erik Brakkee
+ */
+public class Group extends AbstractPersistent implements Serializable, Comparable {
+
+ /**
+ * Group name.
+ */
+ private String _name;
+
+ /**
+ * Constructs the group.
+ * @param aName
+ */
+ Group(String aName) {
+ super();
+ _name = aName;
+ }
+
+ public Group(Group aGroup) {
+ super(aGroup);
+ _name = aGroup._name;
+ }
+
+ protected Group() {
+ super();
+ _name = null;
+ }
+
+ /**
+ * Gets the name of the group.
+ * @return Group name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Sets the group name.
+ * @param aName Group name.
+ */
+ void setName(String aName) {
+ _name = aName;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aGroup) {
+ if ( !( aGroup instanceof Group )) {
+ return false;
+ }
+ return _name.equals(((Group)aGroup)._name);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _name.hashCode();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(T)
+ */
+ public int compareTo(Object aGroup) {
+ return _name.compareTo(((Group)aGroup)._name);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Group(pk = " + getPrimaryKey() + ", name=" + _name + ")";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.Set;
+
+/**
+ * Represents a set of groups. A typical implemnetation would be, a readonly implementation
+ * defined in a configuration file or a list of groups defined in a database.
+ *
+ * @author Erik Brakkee
+ */
+public interface GroupSet {
+
+ /**
+ * Must be called when the group has been modified to notify the group set.
+ * @param aGroup Group that was modified.
+ */
+ void groupModified(Group aGroup);
+
+ /**
+ * Finds the group by name.
+ * @param aName Group name.
+ * @return Group or null if not found.
+ */
+ Group find(String aName);
+
+ /**
+ * Determines if the group exists.
+ * @param aGroup Group.
+ * @return True iff the group exists.
+ */
+ boolean contains(Group aGroup);
+
+ /**
+ * Adds a group. If the group already exists, the existing group set
+ * is left unchanged.
+ * @param aGroup Group.
+ */
+ boolean add(Group aGroup);
+
+ /**
+ * Removes a group. If the group does not exist, this method is a no-op.
+ * @param aGroup Group to remove.
+ * @return True if the group was removed, false otherwise.
+ */
+ boolean remove(Group aGroup);
+
+ /**
+ * Returns the current groups.
+ * @return Groups.
+ */
+ Set<Group> list();
+
+ /**
+ * @return The number of groups.
+ */
+ int size();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * In-memory group set implementation.
+ *
+ * @author Erik Brakkee
+ */
+public class InMemoryGroupSet implements GroupSet {
+
+ /**
+ * Groups.
+ */
+ private Set<Group> _groups;
+
+ /**
+ * Constructs an empty group set.
+ */
+ public InMemoryGroupSet() {
+ _groups = new TreeSet<Group>();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#groupModified(org.wamblee.usermgt.Group)
+ */
+ public void groupModified(Group aGroup) {
+ _groups.remove(aGroup);
+ _groups.add(aGroup);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#find(java.lang.String)
+ */
+ public Group find(String aName) {
+ for (Group group: _groups) {
+ if ( group.getName().equals(aName)) {
+ return new Group(group);
+ }
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#contains(org.wamblee.usermgt.Group)
+ */
+ public boolean contains(Group aGroup) {
+ return _groups.contains(aGroup);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#add(org.wamblee.usermgt.Group)
+ */
+ public boolean add(Group aGroup) {
+ return _groups.add(aGroup);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#remove(org.wamblee.usermgt.Group)
+ */
+ public boolean remove(Group aGroup) {
+ return _groups.remove(aGroup);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#list()
+ */
+ public Set<Group> list() {
+ Set<Group> list = new TreeSet<Group>();
+ for (Group group: _groups) {
+ list.add(new Group(group));
+ }
+ return list;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#size()
+ */
+ public int size() {
+ return _groups.size();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.wamblee.security.encryption.MessageDigester;
+
+/**
+ * In-memory user set.
+ *
+ * @author Erik Brakkee
+ */
+public class InMemoryUserSet extends AbstractUserSet {
+
+ /**
+ * Users. All users in this set have their password validator and encoder set.
+ */
+ private Set<User> _users;
+
+ /**
+ * Constructs an empty user set.
+ */
+ public InMemoryUserSet(NameValidator aPasswordValidator, MessageDigester aPasswordEncoder) {
+ super(aPasswordValidator, aPasswordEncoder);
+ _users = new TreeSet<User>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#userModified(org.wamblee.usermgt.User)
+ */
+ public void userModified(User aUser) {
+ _users.remove(aUser);
+ setPasswordInfo(aUser);
+ _users.add(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#find(java.lang.String)
+ */
+ public User find(String aName) {
+ for (User user : _users) {
+ if (user.getName().equals(aName)) {
+ return new User(user);
+ }
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#add(org.wamblee.usermgt.User)
+ */
+ public boolean add(User aUser) {
+ setPasswordInfo(aUser);
+ return _users.add(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#contains(org.wamblee.usermgt.User)
+ */
+ public boolean contains(User aUser) {
+ return _users.contains(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#remove(org.wamblee.usermgt.User)
+ */
+ public boolean remove(User aUser) {
+ return _users.remove(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#list()
+ */
+ public Set<User> list() {
+ Set<User> list = new TreeSet<User>();
+ for (User user : _users) {
+ list.add(new User(user));
+ }
+ return list;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#list(org.wamblee.usermgt.Group)
+ */
+ public Set<User> list(Group aGroup) {
+ Set<User> result = new TreeSet<User>();
+ for (User user : _users) {
+ if (user.getGroups().contains(aGroup)) {
+ result.add(new User(user));
+ }
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.UserSet#size()
+ */
+ public int size() {
+ return _users.size();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+/**
+ * Implementation of the user accessor that retrieves user information
+ * from JAAS.
+ *
+ * @author Erik Brakkee
+ */
+public class JaasUserAccessor implements UserAccessor {
+
+ /**
+ * User administration to use.
+ */
+ private UserAdministration _admin;
+
+ /**
+ * Class of the JAAS user principal.
+ */
+ private Class _userPrincipalClass;
+
+ /**
+ * Constructs user accessor.
+ * @param aAdmin User administration.
+ * @param aUserClassName Class name of the user principal.
+ */
+ public JaasUserAccessor(UserAdministration aAdmin, String aUserClassName) {
+ _admin = aAdmin;
+ try {
+ _userPrincipalClass = Class.forName(aUserClassName);
+ if ( !Principal.class.isAssignableFrom(_userPrincipalClass)) {
+ throw new IllegalArgumentException("Specified class '" + aUserClassName + "' is not a subclass of '" +
+ Principal.class.getName());
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAccessor#getCurrentUser()
+ */
+ public User getCurrentUser() {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject == null) {
+ return null;
+ }
+ Principal userPrincipal = getUserPrincipal(subject);
+
+ return _admin.getUser(userPrincipal.getName());
+ }
+
+ /**
+ * Gets the user principal from the subject.
+ * @param subject Subject.
+ * @return User principal.
+ * @throws IllegalArgumentException In case there is a duplicate principal or the principal was not found.
+ */
+ private Principal getUserPrincipal(Subject subject) {
+ Set<Principal> principals = subject.getPrincipals();
+ Principal userPrincipal = null;
+ for ( Principal principal: principals) {
+ if ( principal.getClass().equals(_userPrincipalClass)) {
+ if ( userPrincipal != null ) {
+ throw new IllegalArgumentException(
+ "Multiple principals for class '" + _userPrincipalClass + "', subject: " + subject);
+ }
+ userPrincipal = principal;
+ }
+ }
+ if ( userPrincipal == null ) {
+ throw new IllegalArgumentException(
+ "No user principal found for class '" + _userPrincipalClass + "', subject: " + subject);
+ }
+ return userPrincipal;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+/**
+ * Validator of names.
+ *
+ * @author Erik Brakkee
+ */
+public interface NameValidator {
+
+ /**
+ * Validates a name.
+ * @param aName Name
+ * @throws UserMgtException In case the name is invalid.
+ */
+ void validate(String aName) throws UserMgtException;
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Validation of names based on a regular expression.
+ *
+ * @author Erik Brakkee
+ */
+public class RegexpNameValidator implements NameValidator {
+
+ /**
+ * Convenience pattern for an id.
+ */
+ public static final String ID_PATTERN = "[a-zA-Z]+[a-zA-Z0-9]*";
+
+ /**
+ * Convenience pattern for a password consisting of at least 6 characters.
+ */
+ public static final String PASSWORD_PATTERN = ".{6}.*";
+
+ /**
+ * Pattern to use.
+ */
+ private String _pattern;
+
+ /**
+ * Reason to use when validation fails.
+ */
+ private Reason _reason;
+
+ /**
+ * Message to report.
+ */
+ private String _message;
+
+ /**
+ * Validates a regular expression.
+ * @param aPattern Pattern that names must comply to.
+ * @param aReason Reason to report when validation fails.
+ * @param aMessage Message to report.
+ */
+ public RegexpNameValidator(String aPattern, Reason aReason, String aMessage) {
+ _pattern = aPattern;
+ _reason = aReason;
+ _message = aMessage;
+ }
+
+ /**
+ * Convenience constructor with all string parameters. Useful for configuration
+ * in Spring.
+ * @param aPattern Pattern to use.
+ * @param aReason Reason.
+ * @param aMessage Message.
+ */
+ public RegexpNameValidator(String aPattern, String aReason, String aMessage) {
+ this(aPattern, Reason.valueOf(aReason), aMessage);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.NameValidator#validate(java.lang.String)
+ */
+ public void validate(String aName) throws UserMgtException {
+ if ( !aName.matches(_pattern)) {
+ throw new UserMgtException(_reason, _message);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.io.Serializable;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.wamblee.persistence.AbstractPersistent;
+import org.wamblee.security.encryption.MessageDigester;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Represents a user.
+ * The methods for managing the groups of the user have package scope.
+ * Managing the groups of the user should be done through the
+ * {@link org.wamblee.usermgt.UserAdministration} interface.
+ */
+public class User extends AbstractPersistent implements Serializable, Comparable {
+
+ /**
+ * User name.
+ */
+ private String _name;
+
+ /**
+ * Password.
+ */
+ private String _password;
+
+ /**
+ * Groups the user belongs to.
+ */
+ private Set<Group> _groups;
+
+ /**
+ * Password validator.
+ */
+ private NameValidator _passwordValidator;
+
+ /**
+ * Password encoder.
+ */
+ private MessageDigester _passwordEncoder;
+
+ /**
+ * Constructs the user.
+ * @param aName User name.
+ * @param aPassword Password.
+ * @param aGroup Group the user belongs to.
+ */
+ User(String aName, String aPassword, Group aGroup, NameValidator aPasswordValidator,
+ MessageDigester aPasswordEncoder) throws UserMgtException {
+ super();
+ _name = aName;
+ aPasswordValidator.validate(aPassword);
+ _password = aPasswordEncoder.hash(aPassword);
+ _groups = new TreeSet<Group>();
+ _groups.add(aGroup);
+ _passwordValidator = aPasswordValidator;
+ _passwordEncoder = aPasswordEncoder;
+ }
+
+ public User(User aUser) {
+ super(aUser);
+ _name = aUser._name;
+ _password = aUser._password;
+ _groups = new TreeSet<Group>();
+ for (Group group: aUser._groups) {
+ _groups.add(new Group(group));
+ }
+ _passwordValidator = aUser._passwordValidator;
+ _passwordEncoder = aUser._passwordEncoder;
+ }
+
+ User() {
+ super();
+ _name = null;
+ _password = null;
+ _groups = null;
+ _passwordValidator = null;
+ _passwordEncoder = null;
+ }
+
+ /**
+ * Sets the password validator.
+ * @param aPasswordValidator Validator.
+ */
+ public void setPasswordValidator(NameValidator aPasswordValidator) {
+ _passwordValidator = aPasswordValidator;
+ }
+
+ /**
+ * Sets the password encoder.
+ * @param aPasswordEncoder Encoder.
+ */
+ public void setPasswordEncoder(MessageDigester aPasswordEncoder) {
+ _passwordEncoder = aPasswordEncoder;
+ }
+
+ /**
+ * @return Returns the _password.
+ */
+ String getPassword() {
+ return _password;
+ }
+
+ /**
+ * Checks the password.
+ * @param aPassword Password to check.
+ * @throws UserMgtException In case the password is incorrect.
+ */
+ public void checkPassword(String aPassword) throws UserMgtException {
+ String encoded = _passwordEncoder.hash(aPassword);
+ if ( !_password.equals(encoded) ) {
+ throw new UserMgtException(Reason.INVALID_PASSWORD, this);
+ }
+ }
+
+ /**
+ * Changes the password.
+ * @param aOldPassword Old password.
+ * @param aNewPassword New password.
+ * @throws UserMgtException In case the old password is incorrect.
+ */
+ public void changePassword(String aOldPassword, String aNewPassword) throws UserMgtException {
+ checkPassword(aOldPassword);
+ _passwordValidator.validate(aNewPassword);
+ setPassword(aNewPassword);
+ }
+
+ /**
+ * @param aPassword
+ * The password to set.
+ */
+ public void setPassword(String aPassword) throws UserMgtException {
+ _passwordValidator.validate(aPassword);
+ _password = _passwordEncoder.hash(aPassword);
+ }
+
+ /**
+ * For OR mapping.
+ * @return Password.
+ */
+ protected String getPasswordString() {
+ return _password;
+ }
+
+ /**
+ * For OR mapping.
+ * @param aPassword Password.
+ */
+ protected void setPasswordString(String aPassword) {
+ _password = aPassword;
+ }
+
+ /**
+ * @return Returns the _user.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * @param aName
+ * The username to set.
+ */
+ void setName(String aName) {
+ _name = aName;
+ }
+
+ /**
+ * Gets the groups the user belongs to.
+ * @return Groups.
+ */
+ public Set<Group> getGroups() {
+ Set<Group> result = new TreeSet<Group>();
+ result.addAll(_groups);
+ return result;
+ }
+
+ /**
+ * Checks whether the user belongs to the given group.
+ * @param aGroup Group.
+ * @return True if the user belongs to the group.
+ */
+ public boolean isInGroup(Group aGroup) {
+ return _groups.contains(aGroup);
+ }
+
+ /**
+ * Checks whether the user belongs to the given group.
+ * @param aGroup Group.
+ * @return True if the user belongs to the group.
+ */
+ public boolean isInGroup(String aGroup) {
+ return _groups.contains(new Group(aGroup));
+ }
+
+ /**
+ * Gets the group set. For OR mapping.
+ * @return set of groups.
+ */
+ Set<Group> getGroupSet() {
+ return _groups;
+ }
+
+ /**
+ * Sets the groups the user belongs to, for OR mapping.
+ * @param aGroups Groups.
+ */
+ void setGroupSet(Set<Group> aGroups) {
+ _groups = aGroups;
+ }
+
+ /**
+ * Adds the user to a group.
+ * @param aGroup Group to add the user to.
+ * @throws UserMgtException In case the user already belongs to the group.
+ */
+ void addGroup(Group aGroup) throws UserMgtException {
+ if (_groups.contains(aGroup)) {
+ throw new UserMgtException(Reason.USER_ALREADY_IN_GROUP, aGroup);
+ }
+ _groups.add(aGroup);
+ }
+
+ /**
+ * Removes the user from a group.
+ * @param aGroup Group.
+ * @throws UserMgtException In case the user does not belong to the group.
+ */
+ void removeGroup(Group aGroup) throws UserMgtException {
+ if (!_groups.contains(aGroup)) {
+ throw new UserMgtException(Reason.USER_NOT_IN_GROUP, this, aGroup);
+ }
+ if ( _groups.size() == 1 ) {
+ throw new UserMgtException(Reason.USER_MUST_BE_IN_A_GROUP, this, aGroup);
+ }
+ _groups.remove(aGroup);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aUser) {
+ if ( !(aUser instanceof User)) {
+ return false;
+ }
+ User user = (User)aUser;
+ return _name.equals(user._name);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _name.hashCode();
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ String result = "User(name=" + _name + ", password=" + _password;
+ for (Group group: _groups) {
+ result += ", group=" + group;
+ }
+ return result + ")";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(T)
+ */
+ public int compareTo(Object aUser) {
+ return _name.compareTo(((User)aUser)._name);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+/**
+ * Interface for accessing the currently logged in user.
+ *
+ * @author Erik Brakkee
+ */
+public interface UserAccessor {
+ /**
+ * Gets the current user.
+ * @return Currently logged in user or null if no user is found.
+ */
+ User getCurrentUser();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.log4j.Logger;
+
+/**
+ * User administration initializer. It populates the user administration with a
+ * number of groups and users but only in case no users exist.
+ *
+ * @author Erik Brakkee
+ */
+public class UserAdminInitializer {
+
+ private static final Logger LOGGER = Logger.getLogger(UserAdminInitializer.class);
+
+ /**
+ * Initializes the user administration in case no users are present.
+ *
+ */
+ public UserAdminInitializer(UserAdministration aAdmin, String[] aUsers,
+ String[] aGroups, String[] aPasswords) throws UserMgtException, NoSuchAlgorithmException {
+ if (aUsers.length != aGroups.length
+ || aUsers.length != aPasswords.length) {
+ throw new IllegalArgumentException(
+ "Array sizes for users, groups, and passwords differ: "
+ + aUsers.length + "," + aGroups.length + ","
+ + aPasswords.length);
+
+ }
+ if (aAdmin.getUserCount() == 0) {
+ initialize(aAdmin, aUsers, aGroups, aPasswords);
+ }
+
+ }
+
+ /**
+ * Adds the specified users and groups to the user administration.
+ * @param aAdmin User administration.
+ * @param aUsers Users.
+ * @param aGroups Groups.
+ * @param aPasswords Passwords.
+ * @throws UserMgtException In case of a problem creating users or groups.
+ */
+ private void initialize(UserAdministration aAdmin, String[] aUsers,
+ String[] aGroups, String[] aPasswords) throws UserMgtException {
+ for (int i = 0; i < aUsers.length; i++) {
+ String user = aUsers[i];
+ String group = aGroups[i];
+ String password = aPasswords[i];
+
+ if (aAdmin.getUser(user) == null) {
+ // must create user.
+ Group groupObj = aAdmin.getGroup(group);
+ if (groupObj == null) {
+ // must create group
+ LOGGER.info("Creating group: " + group);
+ groupObj = aAdmin.createGroup(group);
+ }
+ assert groupObj != null;
+
+ LOGGER.info("Creating user: " + user + " password: " + password);
+ aAdmin.createUser(user, password, groupObj);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.Set;
+
+/**
+ * Interface for user administration. Manages the users and groups.
+ *
+ * @author Erik Brakkee
+ */
+public interface UserAdministration {
+
+ /**
+ * Creates a new user.
+ * @param aUser Username.
+ * @param aPassword Password.
+ * @param aGroup Group.
+ * @return User.
+ * @throws UserMgtException In case there is a conflict with an existing user.
+ */
+ User createUser(String aUser, String aPassword, Group aGroup) throws UserMgtException;
+
+ /**
+ * Creates a new group.
+ * @param aName Group name.
+ * @return Group
+ * @throws UserMgtException In case there is a conflict with an existing group.
+ */
+ Group createGroup(String aName) throws UserMgtException;
+
+ /**
+ * @return Number of users.
+ */
+ int getUserCount();
+
+ /**
+ * @return Number of groups.
+ */
+ int getGroupCount();
+
+ /**
+ * Must be called when the user is modified.
+ * @param aUser User.
+ */
+ void userModified(User aUser);
+
+ /**
+ * Must be called when the group is modified.
+ * @param aGroup Group.
+ */
+ void groupModified(Group aGroup);
+
+ /**
+ * Gets the user for a given name.
+ * @param aName User name.
+ * @return User or null if not found.
+ */
+ User getUser(String aName);
+
+ /**
+ * Gets the group for a given group name.
+ * @param aName Group name.
+ * @return Group or null if not found.
+ */
+ Group getGroup(String aName);
+
+ /**
+ * Get the users.
+ * @return All known users.
+ */
+ Set<User> getUsers();
+
+ /**
+ * Gets the users for a given group.
+ * @param aGroup Group.
+ * @return Set of users (always non-null).
+ */
+ Set<User> getUsers(Group aGroup);
+
+ /**
+ * Gets all known groups.
+ * @return Groups.
+ */
+ Set<Group> getGroups();
+
+ /**
+ * Renames a user.
+ * @param aUser User object for which user name must be changed.
+ * @param aUserName New user name.
+ * @throws UserMgtException In case the user is not known or the new user
+ * name is already in use by another user.
+ */
+ void renameUser(User aUser, String aUserName) throws UserMgtException;
+
+ /**
+ * Renames a group.
+ * @param aGroup Group to rename.
+ * @param aGroupName New name for the group.
+ * @throws UserMgtException In case the new group name is already used by
+ * another group of if the existing group is unknown.
+ */
+ void renameGroup(Group aGroup, String aGroupName) throws UserMgtException;
+
+ /**
+ * Removes the user.
+ * @param aUser User to remove.
+ * @throws UserMgtException In case the user does not exist.
+ */
+ void removeUser(User aUser) throws UserMgtException;
+
+ /**
+ * Removes the group.
+ * @param aGroup Group to remove.
+ * @throws UserMgtException In case there are still users that are in the given group.
+ */
+ void removeGroup(Group aGroup) throws UserMgtException;
+
+ /**
+ * Adds a user to a group.
+ * @param aUser User.
+ * @param aGroup Group.
+ * @throws UserMgtException In case the user or group or not known or if the user
+ * is already part of the group.
+ */
+ void addUserToGroup(User aUser, Group aGroup) throws UserMgtException;
+
+ /**
+ * Removes a user from a group.
+ * @param aUser User
+ * @param aGroup Group
+ * @throws UserMgtException In case the user or group are unknown or if the user
+ * is not part of the group.
+ */
+ void removeUserFromGroup(User aUser, Group aGroup) throws UserMgtException;
+}
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import static org.wamblee.usermgt.UserMgtException.Reason.DUPLICATE_GROUP;
+import static org.wamblee.usermgt.UserMgtException.Reason.DUPLICATE_USER;
+import static org.wamblee.usermgt.UserMgtException.Reason.GROUP_STILL_OCCUPIED;
+import static org.wamblee.usermgt.UserMgtException.Reason.TRIVIAL_RENAME;
+import static org.wamblee.usermgt.UserMgtException.Reason.UNKNOWN_GROUP;
+import static org.wamblee.usermgt.UserMgtException.Reason.UNKNOWN_USER;
+
+import java.util.Set;
+
+/**
+ * Administration of users and groups.
+ *
+ * @author Erik Brakkee
+ */
+public class UserAdministrationImpl implements UserAdministration {
+
+ /**
+ * All known users.
+ */
+ private UserSet _users;
+
+ /**
+ * All known groups.
+ */
+ private GroupSet _groups;
+
+ /**
+ * Validator for user names.
+ */
+ private NameValidator _userValidator;
+
+ /**
+ * Validator for group names.
+ */
+ private NameValidator _groupValidator;
+
+ /**
+ * Constructs empty user administration.
+ *
+ */
+ public UserAdministrationImpl(UserSet aUsers, GroupSet aGroups, NameValidator aUserValidator,
+ NameValidator aGroupValidator) {
+ _users = aUsers;
+ _groups = aGroups;
+ _userValidator = aUserValidator;
+ _groupValidator = aGroupValidator;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#createUser(java.lang.String,
+ * java.lang.String)
+ */
+ public User createUser(String aUser, String aPassword, Group aGroup)
+ throws UserMgtException {
+ _userValidator.validate(aUser);
+ checkGroup(aGroup);
+ User user = _users.createUser(aUser, aPassword, aGroup);
+ return new User(user);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#createGroup(java.lang.String)
+ */
+ public Group createGroup(String aName) throws UserMgtException {
+ _groupValidator.validate(aName);
+ Group group = new Group(aName);
+ if (_groups.contains(group)) {
+ throw new UserMgtException(DUPLICATE_GROUP, group);
+ }
+ _groups.add(group);
+ return new Group(group);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#userModified(org.wamblee.usermgt.User)
+ */
+ public void userModified(User aUser) {
+ _users.userModified(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#groupModified(org.wamblee.usermgt.Group)
+ */
+ public void groupModified(Group aGroup) {
+ _groups.groupModified(aGroup);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#getUser(java.lang.String)
+ */
+ public User getUser(String aName) {
+ return _users.find(aName);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#getGroup(java.lang.String)
+ */
+ public Group getGroup(String aName) {
+ return _groups.find(aName);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#getUsers()
+ */
+ public Set<User> getUsers() {
+ return _users.list();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#getUsers(org.wamblee.usermgt.Group)
+ */
+ public Set<User> getUsers(Group aGroup) {
+ return _users.list(aGroup);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#getGroups()
+ */
+ public Set<Group> getGroups() {
+ return _groups.list();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#removeUser(org.wamblee.usermgt.User)
+ */
+ public void removeUser(User aUser) throws UserMgtException {
+ checkUser(aUser);
+ _users.remove(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#removeGroup(org.wamblee.usermgt.Group)
+ */
+ public void removeGroup(Group aGroup) throws UserMgtException {
+ checkGroup(aGroup);
+ if (getUsers(aGroup).size() > 0) {
+ throw new UserMgtException(GROUP_STILL_OCCUPIED, aGroup);
+ }
+ _groups.remove(aGroup);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#renameUser(org.wamblee.usermgt.User,
+ * java.lang.String)
+ */
+ public void renameUser(User aUser, String aUserName)
+ throws UserMgtException {
+ checkUser(aUser);
+ if (aUser.getName().equals(aUserName)) {
+ throw new UserMgtException(TRIVIAL_RENAME, aUser);
+ }
+ if (_users.find(aUserName) != null) {
+ throw new UserMgtException(DUPLICATE_USER, aUser);
+ }
+ _userValidator.validate(aUserName);
+ // we are modifying the user so we should re-insert it into the set
+ // after renaming it.
+ _users.remove(aUser);
+ aUser.setName(aUserName);
+ _users.add(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#renameGroup(org.wamblee.usermgt.Group,
+ * java.lang.String)
+ */
+ public void renameGroup(Group aGroup, String aGroupName)
+ throws UserMgtException {
+ checkGroup(aGroup);
+ if (aGroup.getName().equals(aGroupName)) {
+ throw new UserMgtException(TRIVIAL_RENAME, aGroup);
+ }
+ if (_groups.find(aGroupName) != null) {
+ throw new UserMgtException(DUPLICATE_GROUP, aGroup);
+ }
+ _groupValidator.validate(aGroupName);
+ // we are renaming the group so we should re-insert it into the set
+ // after renaming it.
+ _groups.remove(aGroup);
+ aGroup.setName(aGroupName);
+ _groups.add(aGroup);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#addUserToGroup(org.wamblee.usermgt.User,
+ * org.wamblee.usermgt.Group)
+ */
+ public void addUserToGroup(User aUser, Group aGroup)
+ throws UserMgtException {
+ checkUser(aUser);
+ checkGroup(aGroup);
+ aUser.addGroup(aGroup);
+ _users.userModified(aUser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministration#removeUserFromGroup(org.wamblee.usermgt.User,
+ * org.wamblee.usermgt.Group)
+ */
+ public void removeUserFromGroup(User aUser, Group aGroup)
+ throws UserMgtException {
+ checkUser(aUser);
+ checkGroup(aGroup);
+ aUser.removeGroup(aGroup);
+ _users.userModified(aUser);
+ }
+
+ /**
+ * @param aUser
+ * @throws UserMgtException
+ */
+ private void checkUser(User aUser) throws UserMgtException {
+ if (!_users.contains(aUser)) {
+ throw new UserMgtException(UNKNOWN_USER, aUser);
+ }
+ }
+
+ /**
+ * @param aGroup
+ * @throws UserMgtException
+ */
+ private void checkGroup(Group aGroup) throws UserMgtException {
+ if (!_groups.contains(aGroup)) {
+ throw new UserMgtException(UNKNOWN_GROUP, aGroup);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.UserAdministration#getUserCount()
+ */
+ public int getUserCount() {
+ return _users.size();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.UserAdministration#getGroupCount()
+ */
+ public int getGroupCount() {
+ return _groups.size();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import net.sf.ehcache.Ehcache;
+
+import org.hibernate.SessionFactory;
+import org.wamblee.cache.EhCache;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.spring.SpringComponent;
+
+public class UserGroupRepositoryComponent extends SpringComponent {
+
+ public UserGroupRepositoryComponent(String aName) {
+ super(aName, new String[] { "spring/org.wamblee.security.usermgt-repositories.xml" } ,
+ createProvided(), createRequired());
+
+ }
+
+ private static Map<RequiredInterface, String> createRequired() {
+ Map<RequiredInterface, String> required = new HashMap<RequiredInterface, String>();
+ required.put(new DefaultRequiredInterface("sessionFactory", SessionFactory.class), "sessionFactory");
+ return required;
+ }
+
+ private static Map<String, ProvidedInterface> createProvided() {
+ Map<String,ProvidedInterface> provided = new HashMap<String,ProvidedInterface>();
+ provided.put("userCache", new DefaultProvidedInterface("cache", EhCache.class));
+ provided.put(UserSet.class.getName(), new DefaultProvidedInterface("userset", UserSet.class));
+ provided.put(GroupSet.class.getName(), new DefaultProvidedInterface("groupset", GroupSet.class));
+ return provided;
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.EnumMap;
+
+/**
+ * User management exception.
+ *
+ * @author Erik Brakkee
+ */
+public class UserMgtException extends Exception {
+
+ static final long serialVersionUID = 5585349754997507529L;
+
+ /**
+ * Possible causes for the exception.
+ *
+ */
+ public enum Reason {
+ UNKNOWN_USER,
+ UNKNOWN_GROUP,
+ DUPLICATE_USER,
+ DUPLICATE_GROUP,
+ USER_ALREADY_IN_GROUP,
+ USER_NOT_IN_GROUP,
+ TRIVIAL_RENAME,
+ INVALID_PASSWORD,
+ GROUP_STILL_OCCUPIED,
+ USER_MUST_BE_IN_A_GROUP,
+ INVALID_USERNAME,
+ INVALID_GROUPNAME
+ }
+
+ /**
+ * Mapping of enum to exception message text.
+ */
+ private static final EnumMap<Reason,String> MESSAGES = new EnumMap<Reason,String>(Reason.class);
+
+ static {
+ MESSAGES.put(Reason.UNKNOWN_USER, "Unknown user");
+ MESSAGES.put(Reason.UNKNOWN_GROUP, "Unknown group");
+ MESSAGES.put(Reason.DUPLICATE_USER, "Duplicate user");
+ MESSAGES.put(Reason.DUPLICATE_GROUP, "Duplicate group");
+ MESSAGES.put(Reason.USER_ALREADY_IN_GROUP, "User already in group");
+ MESSAGES.put(Reason.USER_NOT_IN_GROUP, "User not in group");
+ MESSAGES.put(Reason.TRIVIAL_RENAME, "Trivial rename");
+ MESSAGES.put(Reason.INVALID_PASSWORD, "Invalid password");
+ MESSAGES.put(Reason.GROUP_STILL_OCCUPIED, "Group still occupied");
+ MESSAGES.put(Reason.USER_MUST_BE_IN_A_GROUP, "User must be in at least one group");
+ MESSAGES.put(Reason.INVALID_USERNAME, "Invalid user name");
+ MESSAGES.put(Reason.INVALID_GROUPNAME, "Invalid group name");
+ }
+
+ /**
+ * Cause of the exception.
+ */
+ private Reason _cause;
+
+ /**
+ * User or null if no user is relevant for the problem.
+ */
+ private User _user;
+
+ /**
+ * Group or null if no group is relevant for the problem.
+ */
+ private Group _group;
+
+ public UserMgtException(Reason aCause, String aMessage) {
+ super(MESSAGES.get(aCause) + ": " + aMessage);
+ _cause = aCause;
+ }
+
+ public UserMgtException(Reason aCause, User aUser) {
+ this(aCause, "for user '" + aUser.getName() + "'");
+ _user = aUser;
+ }
+
+ public UserMgtException(Reason aCause, Group aGroup) {
+ this(aCause, "for group '" + aGroup.getName() + "'");
+ _group = aGroup;
+ }
+
+ public UserMgtException(Reason aCause, User aUser, Group aGroup) {
+ this(aCause, "for user '" + aUser.getName() + "' and group '" + aGroup.getName() + "'");
+ _user = aUser;
+ _group = aGroup;
+ }
+
+ /**
+ * Gets the cause of the problem.
+ * @return Cause.
+ */
+ public Reason getReason() {
+ return _cause;
+ }
+
+ /**
+ * Gets the user for which the problem occurred.
+ * @return User or null if not applicable.
+ */
+ public User getUser() {
+ return _user;
+ }
+
+ /**
+ * Gets the group for which the problem occured.
+ * @return Group or null if not applicable.
+ */
+ public Group getGroup() {
+ return _group;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.Set;
+
+/**
+ * Represents a set of users.
+ * Typical implementations would be an implementation based on a static configuration file or
+ * an implementation backed by a database.
+ *
+ * @author Erik Brakkee
+ */
+public interface UserSet {
+
+ /**
+ * Creates a user.
+ * @param aUsername User name.
+ * @param aPassword Password.
+ * @param aGroup Group.
+ * @return New user.
+ * @throws UserMgtException In case the user cannot be created.
+ */
+ User createUser(String aUsername, String aPassword, Group aGroup) throws UserMgtException;
+
+ /**
+ * Must be called whenever a user object has been modified to notify the
+ * user set.
+ * @param aUser Modified user.
+ */
+ void userModified(User aUser);
+
+ /**
+ * Finds user.
+ * @param aName Username.
+ * @return User or null if not found.
+ */
+ User find(String aName);
+
+ /**
+ * Checks if a user exists.
+ * @param aUser User.
+ * @return True iff the user exists.
+ */
+ boolean contains(User aUser);
+
+ /**
+ * Adds a user. If the user already exists, the user details are updated with that
+ * of the specified user object.
+ * @param aUser User to add.
+ */
+ boolean add(User aUser);
+
+ /**
+ * Removes a user. If the user does not exist, nothing happens.
+ * @param aUser
+ */
+ boolean remove(User aUser);
+
+ /**
+ * Lists the current users.
+ * @return Users.
+ */
+ Set<User> list();
+
+ /**
+ * Lists the users belonging to a particular group.
+ * @param aGroup Group.
+ * @return Groups.
+ */
+ Set<User> list(Group aGroup);
+
+ /**
+ *
+ * @return The number of users.
+ */
+ int size();
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.io.IOException;
+
+import javax.sql.DataSource;
+
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.wamblee.cache.EhCache;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.security.authorization.hibernate.AuthorizationMappingFiles;
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.components.ORMappingConfig;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.component.HibernateComponent;
+import org.wamblee.usermgt.UserAccessor;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserGroupRepositoryComponent;
+
+public class AuthorizationComponent extends DefaultContainer {
+
+ private ProvidedInterface TRANSACTION_MGR = new DefaultProvidedInterface(
+ "transactionManager", PlatformTransactionManager.class);
+ private ProvidedInterface HIBERNATE_TEMPLATE = new DefaultProvidedInterface(
+ "hibernateTemplate", HibernateTemplate.class);
+ private ProvidedInterface AUTHORIZATION_SERVICE = new DefaultProvidedInterface(
+ "authorizationService", AuthorizationService.class);
+
+ public AuthorizationComponent(String aName, boolean aExposeInternals)
+ throws IOException {
+ super(aName);
+
+ ObjectConfiguration authConfig = new ObjectConfiguration(AuthorizationMappingFiles.class);
+ authConfig.getSetterConfig().initAllSetters();
+ addComponent("mappingFiles", new AuthorizationMappingFiles(), authConfig);
+
+ Component<?> hibernate = new HibernateComponent("hibernate");
+ addComponent(hibernate);
+
+ Component<?> authorization = new AuthorizationLightComponent("authorization");
+ addComponent(authorization);
+
+ addRequiredInterface(new DefaultRequiredInterface("datasource", DataSource.class));
+ addRequiredInterface(new DefaultRequiredInterface("userAccessor",
+ UserAccessor.class));
+ addRequiredInterface(new DefaultRequiredInterface("ormconfig", ORMappingConfig.class));
+
+ if (aExposeInternals) {
+ addProvidedInterface(TRANSACTION_MGR);
+ addProvidedInterface(HIBERNATE_TEMPLATE);
+ }
+ addProvidedInterface(AUTHORIZATION_SERVICE);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.spring.SpringComponent;
+import org.wamblee.usermgt.GroupSet;
+import org.wamblee.usermgt.UserAccessor;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserSet;
+
+/**
+ * Light version of the user administration component that requires external
+ * datasource, and userset and group set components, as well as an external
+ * hibernate session factory.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class AuthorizationLightComponent extends SpringComponent {
+
+ public AuthorizationLightComponent(String aName) {
+ super(
+ aName,
+ new String[] { "spring/org.wamblee.security.authorization.xml" },
+ createProvided(), createRequired());
+ }
+
+ private static Map<RequiredInterface, String> createRequired() {
+ Map<RequiredInterface, String> required = new HashMap<RequiredInterface, String>();
+ required.put(new DefaultRequiredInterface("userArccessor",
+ UserAccessor.class), UserAccessor.class.getName());
+ required.put(new DefaultRequiredInterface("hibernateTemplate",
+ HibernateTemplate.class), HibernateTemplate.class.getName());
+ return required;
+ }
+
+ private static Map<String, ProvidedInterface> createProvided() {
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put(AuthorizationService.class.getName(),
+ new DefaultProvidedInterface(AuthorizationService.class
+ .getName(), AuthorizationService.class));
+ return provided;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.wamblee.persistence.hibernate.HibernateSupport;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.GroupSet;
+
+/**
+ * Set of groups backed by the database.
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateGroupSet extends HibernateSupport implements GroupSet {
+
+
+ private static final String QUERY_FIND_BY_NAME = "findGroupByName";
+
+ private static final String PARAM_NAME = "name";
+
+ private static final String QUERY_COUNT_GROUPS = "countGroups";
+
+ public HibernateGroupSet() {
+ // Empty
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#groupModified(org.wamblee.usermgt.Group)
+ */
+ public void groupModified(Group aGroup) {
+ assert aGroup.getPrimaryKey() != null;
+ super.merge(aGroup);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#find(java.lang.String)
+ */
+ public Group find(String aName) {
+ List list = getHibernateTemplate().findByNamedQueryAndNamedParam(QUERY_FIND_BY_NAME, PARAM_NAME, aName);
+ if ( list.size() > 1 ) {
+ throw new RuntimeException("More than one group with the same name '" + aName + "'");
+ }
+ if ( list.size() == 0 ) {
+ return null;
+ }
+ return new Group((Group)list.get(0));
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#contains(org.wamblee.usermgt.Group)
+ */
+ public boolean contains(Group aGroup) {
+ return find(aGroup.getName()) != null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#add(org.wamblee.usermgt.Group)
+ */
+ public boolean add(Group aGroup) {
+ assert aGroup.getPrimaryKey() == null;
+ if ( contains(aGroup) ) {
+ return false;
+ }
+ super.merge(aGroup);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#remove(org.wamblee.usermgt.Group)
+ */
+ public boolean remove(Group aGroup) {
+ assert aGroup.getPrimaryKey() != null;
+ if ( !contains(aGroup)) {
+ return false;
+ }
+ Group group = (Group) getHibernateTemplate().merge(aGroup);
+ getHibernateTemplate().delete(group);
+ aGroup.setPrimaryKey(null);
+ aGroup.setPersistedVersion(-1);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#list()
+ */
+ public Set<Group> list() {
+ Set<Group> groups = new TreeSet<Group>();
+ List<Group> list = getHibernateTemplate().loadAll(Group.class);
+ for (Group group: list) {
+ groups.add(new Group(group));
+ }
+ return groups;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.GroupSet#size()
+ */
+ public int size() {
+ Long result = (Long) getHibernateTemplate().findByNamedQuery(QUERY_COUNT_GROUPS).get(0);
+ return result.intValue();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.hibernate.SessionFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.cache.Cache;
+import org.wamblee.persistence.hibernate.HibernateSupport;
+import org.wamblee.security.encryption.MessageDigester;
+import org.wamblee.usermgt.AbstractUserSet;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.NameValidator;
+import org.wamblee.usermgt.User;
+
+/**
+ * User set backed by the database.
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateUserSet extends AbstractUserSet {
+
+ private static final String QUERY_FIND_BY_NAME = "findUserByName";
+
+ private static final String QUERY_FIND_BY_GROUP_NAME = "findUserByGroupName";
+
+ private static final String PARAM_NAME = "name";
+
+ private static final String QUERY_COUNT_USERS = "countUsers";
+
+ /**
+ * Cache of users. Every user in the cache has its password validator and encoder set.
+ */
+ private Cache<String, User> _cache;
+
+ /**
+ * Spring hibernate support.
+ */
+ private HibernateSupport _hibernateSupport;
+
+ /**
+ * Constructs a user set backed by the database.
+ * @param aCache User cache to use.
+ */
+ public HibernateUserSet(Cache<String,User> aCache,
+ NameValidator aPasswordValidator, MessageDigester aPasswordEncoder) {
+ super(aPasswordValidator, aPasswordEncoder);
+ _cache = aCache;
+ _hibernateSupport = new HibernateSupport();
+ }
+
+ /**
+ * Sets the session factory.
+ * @param aFactory Session factory.
+ */
+ public void setSessionFactory(SessionFactory aFactory) {
+ _hibernateSupport.setSessionFactory(aFactory);
+ }
+
+ /**
+ * Gets the hibernate template.
+ * @return Hibernate template.
+ */
+ private HibernateTemplate getHibernateTemplate() {
+ return _hibernateSupport.getHibernateTemplate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#userModified(org.wamblee.usermgt.User)
+ */
+ public void userModified(User aUser) {
+ assert aUser.getPrimaryKey() != null;
+ _hibernateSupport.merge(aUser);
+ _cache.remove(aUser.getName());
+ setPasswordInfo(aUser);
+ _cache.put(aUser.getName(), new User(aUser));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#find(java.lang.String)
+ */
+ public User find(String aName) {
+ User user = _cache.get(aName);
+ if (user != null) {
+ return user;
+ }
+ List result = getHibernateTemplate().findByNamedQueryAndNamedParam(
+ QUERY_FIND_BY_NAME, PARAM_NAME, aName);
+ if (result.size() > 1) {
+ throw new RuntimeException(
+ "Implementation problem, more than one user with the same name!");
+ }
+ if (result.size() == 0) {
+ return null;
+ }
+ user = (User) result.get(0);
+ setPasswordInfo(user);
+ _cache.put(aName, user);
+ return new User(user);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#contains(org.wamblee.usermgt.User)
+ */
+ public boolean contains(User aUser) {
+ return find(aUser.getName()) != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#add(org.wamblee.usermgt.User)
+ */
+ public boolean add(User aUser) {
+ assert aUser.getPrimaryKey() == null;
+ if (contains(aUser)) {
+ return false;
+ }
+ getHibernateTemplate().saveOrUpdate(aUser);
+ setPasswordInfo(aUser);
+ _cache.put(aUser.getName(), aUser);
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#remove(org.wamblee.usermgt.User)
+ */
+ public boolean remove(User aUser) {
+ assert aUser.getPrimaryKey() != null;
+ if (!contains(aUser)) {
+ return false;
+ }
+ User user = (User) getHibernateTemplate().merge(aUser);
+ getHibernateTemplate().delete(user);
+ aUser.setPersistedVersion(-1);
+ aUser.setPrimaryKey(null);
+ _cache.remove(aUser.getName());
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#list()
+ */
+ public Set<User> list() {
+ Set<User> users = new TreeSet<User>();
+ List<User> list = getHibernateTemplate().loadAll(User.class);
+ for (User user : list) {
+ setPasswordInfo(user);
+ users.add(new User(user));
+ }
+ return users;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserSet#list(org.wamblee.usermgt.Group)
+ */
+ public Set<User> list(Group aGroup) {
+ Set<User> users = new TreeSet<User>();
+ List<User> list = getHibernateTemplate().findByNamedQueryAndNamedParam(
+ QUERY_FIND_BY_GROUP_NAME, PARAM_NAME, aGroup.getName());
+ for (User user : list) {
+ setPasswordInfo(user);
+ users.add(new User(user));
+ }
+ return users;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.UserSet#size()
+ */
+ public int size() {
+ Long result = (Long)getHibernateTemplate().findByNamedQuery(QUERY_COUNT_USERS).get(0);
+ return result.intValue();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.io.IOException;
+
+import javax.sql.DataSource;
+
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.wamblee.cache.EhCache;
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.components.ORMappingConfig;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.component.HibernateComponent;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserGroupRepositoryComponent;
+
+public class UserAdministrationComponent extends DefaultContainer {
+
+ private ProvidedInterface TRANSACTION_MGR = new DefaultProvidedInterface(
+ "transactionManager", PlatformTransactionManager.class);
+ private ProvidedInterface USER_CACHE = new DefaultProvidedInterface(
+ "userCache", EhCache.class);
+ private ProvidedInterface HIBERNATE_TEMPLATE = new DefaultProvidedInterface(
+ "hibernateTemplate", HibernateTemplate.class);
+ private ProvidedInterface USER_MGT = new DefaultProvidedInterface(
+ "usermgt", UserAdministration.class);
+
+ public UserAdministrationComponent(String aName, boolean aExposeInternals)
+ throws IOException {
+ super(aName);
+
+ ObjectConfiguration mappingFilesConfig = new ObjectConfiguration(UsermgtHibernateMappingFiles.class);
+ mappingFilesConfig.getSetterConfig().initAllSetters();
+ addComponent("mappingFiles", new UsermgtHibernateMappingFiles(), mappingFilesConfig);
+
+ Component<?> _hibernate = new HibernateComponent("hibernate");
+ addComponent(_hibernate);
+
+ Component<?> _repository = new UserGroupRepositoryComponent("usersgroups");
+ addComponent(_repository);
+
+ Component<?> _usermgt = new UserAdministrationLightComponent("usermgtlight");
+ addComponent(_usermgt);
+
+ addRequiredInterface(new DefaultRequiredInterface("datasource",
+ DataSource.class));
+ addRequiredInterface(new DefaultRequiredInterface("ormconfig", ORMappingConfig.class));
+
+ if (aExposeInternals) {
+ addProvidedInterface(TRANSACTION_MGR);
+ addProvidedInterface(USER_CACHE);
+ addProvidedInterface(HIBERNATE_TEMPLATE);
+ }
+ addProvidedInterface(USER_MGT);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.spring.SpringComponent;
+import org.wamblee.usermgt.GroupSet;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserSet;
+
+/**
+ * Light version of the user administration component that requires external
+ * datasource, and userset and group set components, as well as an external
+ * hibernate session factory.
+ * @author Erik Brakkee
+ *
+ */
+public class UserAdministrationLightComponent extends SpringComponent {
+
+ public UserAdministrationLightComponent(String aName) {
+ super(aName, new String[] { "spring/org.wamblee.security.usermgt.xml" },
+ createProvided(), createRequired() );
+ }
+
+ private static Map<RequiredInterface, String> createRequired() {
+ Map<RequiredInterface, String> required = new HashMap<RequiredInterface,String>();
+ required.put(new DefaultRequiredInterface("userSet", UserSet.class), UserSet.class.getName());
+ required.put(new DefaultRequiredInterface("groupSet", GroupSet.class), GroupSet.class.getName());
+ return required;
+ }
+
+ private static Map<String, ProvidedInterface> createProvided() {
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put(UserAdministration.class.getName(), new DefaultProvidedInterface("org.wamblee.usermgt.UserAdministration",
+ UserAdministration.class));
+ return provided;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.util.Collections;
+
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+
+/**
+ * Hibernate mapping files for user management.
+ *
+ * @author Erik Brakkee
+ */
+public class UsermgtHibernateMappingFiles extends HibernateMappingFiles {
+
+ public UsermgtHibernateMappingFiles() {
+ super(new String[] {
+ "hbm/Group.hbm.xml", "hbm/User.hbm.xml"
+ });
+ }
+
+ public UsermgtHibernateMappingFiles(String[] aFiles) {
+ this();
+ Collections.addAll(this, aFiles);
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+
+ <class name="org.wamblee.security.authorization.AuthorizationRule"
+ table="AUTHORIZATION_RULES"
+ select-before-update="true"
+ lazy="false">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <discriminator column="TYPE" type="string"/>
+ <version name="persistedVersion" column="VERSION"/>
+
+ <subclass name="org.wamblee.security.authorization.UrlAuthorizationRule"
+ discriminator-value="ISINGROUP"
+ lazy="false">
+ <property name="authorizationResultString" column="RESULT"/>
+ <property name="resourceClassName" column="RESOURCE_CLASSNAME"/>
+ <many-to-one name="userCondition"
+ class="org.wamblee.security.authorization.UserCondition"
+ column="USERCONDITION_ID"
+ cascade="all"
+ lazy="false"/>
+ <many-to-one name="pathCondition"
+ class="org.wamblee.security.authorization.PathCondition"
+ column="PATHCONDITION_ID"
+ cascade="all"
+ lazy="false"/>
+ <many-to-one name="operationCondition"
+ class="org.wamblee.security.authorization.OperationCondition"
+ column="OPERATIONCONDITION_ID"
+ cascade="all"
+ lazy="false"/>
+ </subclass>
+
+ </class>
+
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+
+ <class name="org.wamblee.security.authorization.AuthorizationService" table="AUTHORIZATION_SERVICE"
+ select-before-update="true">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <discriminator column="TYPE" type="string"/>
+ <version name="persistedVersion" column="VERSION"/>
+
+ <subclass name="org.wamblee.security.authorization.DefaultAuthorizationService"
+ discriminator-value="DEFAULT">
+
+ <property name="name" column="NAME"/>
+
+ <list name="mappedRules" table="AUTHORIZATION_SERVICE_RULES" lazy="false" cascade="all-delete-orphan">
+ <key column="ID"/>
+ <index column="POSITION"/>
+ <many-to-many class="org.wamblee.security.authorization.AuthorizationRule"
+ column="RULE_ID"/>
+ </list>
+ </subclass>
+
+ </class>
+
+ <query name="findAuthorizationServiceByName">
+ select service
+ from org.wamblee.security.authorization.AuthorizationService service
+ where service.name = :name
+ </query>
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+<hibernate-mapping>
+
+ <class name="org.wamblee.usermgt.Group" table="GROUPS" select-before-update="true">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <version name="persistedVersion" column="VERSION"/>
+ <property name="name" column="NAME" unique="true"/>
+ </class>
+
+ <query name="findGroupByName">
+ from org.wamblee.usermgt.Group grp where grp.name = :name
+ </query>
+
+ <query name="countGroups">
+ select count(*)
+ from org.wamblee.usermgt.Group group
+ </query>
+
+</hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+
+ <class name="org.wamblee.security.authorization.OperationCondition" table="OPERATION_CONDITIONS"
+ select-before-update="true"
+ lazy="false">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <discriminator column="TYPE" type="string"/>
+ <version name="persistedVersion" column="VERSION"/>
+
+ <subclass name="org.wamblee.security.authorization.IsaOperationCondition"
+ discriminator-value="ISA"
+ lazy="false">
+ <property name="operationString" column="OPERATION"/>
+ </subclass>
+
+ </class>
+
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+
+ <class name="org.wamblee.security.authorization.PathCondition" table="PATH_CONDITIONS"
+ select-before-update="true"
+ lazy="false">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <discriminator column="TYPE" type="string"/>
+ <version name="persistedVersion" column="VERSION"/>
+
+ <subclass name="org.wamblee.security.authorization.StartsWithPathCondition"
+ discriminator-value="STARTS_WITH"
+ lazy="false">
+ <property name="path" column="PATH"/>
+ </subclass>
+
+ </class>
+
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+ <subclass name="org.wamblee.photos.authorizationrules.PageAuthorizationRule"
+ extends="org.wamblee.security.authorization.UrlAuthorizationRule"
+ discriminator-value="PAGE"
+ lazy="false"/>
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+
+ <class name="org.wamblee.security.authorization.PathCondition" table="PATH_CONDITIONS"
+ select-before-update="true"
+ lazy="false">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <discriminator column="TYPE" type="string"/>
+ <version name="persistedVersion" column="VERSION"/>
+
+ <subclass name="org.wamblee.security.authorization.RegexpPathCondition"
+ discriminator-value="REGEXP"
+ lazy="false">
+ <property name="pattern" column="PATTERN"/>
+
+ <subclass name="org.wamblee.security.authorization.StartsWithPathCondition"
+ discriminator-value="STARTS_WITH"
+ lazy="false">
+ </subclass>
+
+ </subclass>
+
+ </class>
+
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+ <subclass name="org.wamblee.photos.authorizationrules.PhotoAuthorizationRule"
+ extends="org.wamblee.security.authorization.AuthorizationRule"
+ discriminator-value="PHOTOS"
+ lazy="false"/>
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+ <subclass name="org.wamblee.security.authorization.TestAuthorizationRule"
+ extends="org.wamblee.security.authorization.UrlAuthorizationRule"
+ discriminator-value="TEST"
+ lazy="false"/>
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+<hibernate-mapping>
+
+ <class name="org.wamblee.usermgt.User" table="USERS" select-before-update="true">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <version name="persistedVersion" column="VERSION"/>
+ <property name="name" column="NAME" unique="true"/>
+ <property name="passwordString" column="PASSWORD"/>
+ <set name="groupSet" table="USER_GROUPS" lazy="false">
+ <key column="USER_ID"/>
+ <many-to-many class="org.wamblee.usermgt.Group" column="GROUP_ID"/>
+ </set>
+ </class>
+
+ <query name="findUserByName">
+ from org.wamblee.usermgt.User user where user.name = :name
+ </query>
+
+ <query name="findUserByGroupName">
+ select user
+ from org.wamblee.usermgt.User user
+ join user.groupSet grp
+ where grp.name = :name
+ </query>
+
+ <query name="countUsers">
+ select count(*)
+ from org.wamblee.usermgt.User user
+ </query>
+
+
+</hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+ <hibernate-mapping>
+
+ <class name="org.wamblee.security.authorization.UserCondition" table="USER_CONDITIONS"
+ select-before-update="true"
+ lazy="false">
+ <id name="primaryKey" column="ID" type="long">
+ <generator class="native"/>
+ </id>
+ <discriminator column="TYPE" type="string"/>
+ <version name="persistedVersion" column="VERSION"/>
+
+ <subclass name="org.wamblee.security.authorization.GroupUserCondition"
+ discriminator-value="URL"
+ lazy="false">
+ <property name="group" column="GROUPNAME"/>
+ </subclass>
+
+ <subclass name="org.wamblee.security.authorization.AnyUserCondition"
+ discriminator-value="ANY"
+ lazy="false">
+ </subclass>
+
+ </class>
+
+
+ </hibernate-mapping>
\ No newline at end of file
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="users"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="photos"
+ maxElementsInMemory="1000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+
+###################################################################################
+# dialect
+###################################################################################
+hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
+
+###################################################################################
+# debugging settings: Log4j configuration can provide more detail.
+###################################################################################
+hibernate.show_sql=false
+
+###################################################################################
+# hibernate cache provider
+###################################################################################
+hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
+
+###################################################################################
+# query cache
+###################################################################################
+hibernate.cache.use_query_cache=true
\ No newline at end of file
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="users"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="photos"
+ maxElementsInMemory="1000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="120"
+ timeToLiveSeconds="120"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <bean id="org.wamblee.security.authorization.OperationRegistry"
+ class="org.wamblee.security.authorization.DefaultOperationRegistry">
+ <constructor-arg>
+ <list>
+ <bean class="org.wamblee.security.authorization.AllOperation"/>
+ <bean class="org.wamblee.security.authorization.CreateOperation"/>
+ <bean class="org.wamblee.security.authorization.DeleteOperation"/>
+ <bean class="org.wamblee.security.authorization.ReadOperation"/>
+ <bean class="org.wamblee.security.authorization.WriteOperation"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.security.authorization.AuthorizationService"
+ class="org.wamblee.security.authorization.hibernate.PersistentAuthorizationService">
+ <constructor-arg><value>DEFAULT</value></constructor-arg>
+ <constructor-arg><ref bean="org.springframework.orm.hibernate3.HibernateTemplate"/></constructor-arg>
+ <constructor-arg><ref bean="org.wamblee.usermgt.UserAccessor"/></constructor-arg>
+ <constructor-arg><value type="long">10000</value></constructor-arg>
+ </bean>
+
+ <!-- any user -->
+ <bean id="anyUserCondition" class="org.wamblee.security.authorization.AnyUserCondition">
+ </bean>
+
+ <!-- administrators -->
+ <bean id="adminUserCondition" class="org.wamblee.security.authorization.GroupUserCondition">
+ <constructor-arg><value>administrators</value></constructor-arg>
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="cacheConfig" class="org.wamblee.io.ClassPathResource">
+ <constructor-arg><value>properties/org.wamblee.security.ehcache.xml</value></constructor-arg>
+ </bean>
+
+ <bean id="userCache" class="org.wamblee.cache.EhCache">
+ <constructor-arg><ref local="cacheConfig"/></constructor-arg>
+ <constructor-arg><value>users</value></constructor-arg>
+ </bean>
+
+
+ <bean id="passwordValidator"
+ class="org.wamblee.usermgt.RegexpNameValidator">
+ <constructor-arg><value>.{5,}</value></constructor-arg>
+ <constructor-arg><value>INVALID_PASSWORD</value></constructor-arg>
+ <constructor-arg><value>Password must have at least 5 characters</value></constructor-arg>
+ </bean>
+
+ <bean id="passwordDigester"
+ class="org.wamblee.security.encryption.Md5HexMessageDigester">
+ </bean>
+
+ <bean id="org.wamblee.usermgt.UserSet"
+ class="org.wamblee.usermgt.hibernate.HibernateUserSet">
+ <constructor-arg><ref local="userCache"/></constructor-arg>
+ <constructor-arg><ref local="passwordValidator"/></constructor-arg>
+ <constructor-arg><ref local="passwordDigester"/></constructor-arg>
+
+ <property name="sessionFactory"><ref bean="sessionFactory"/></property>
+
+ </bean>
+
+ <bean id="org.wamblee.usermgt.GroupSet"
+ class="org.wamblee.usermgt.hibernate.HibernateGroupSet">
+
+ <property name="sessionFactory"><ref bean="sessionFactory"/></property>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="org.wamblee.usermgt.UserAdministration-target"
+ class="org.wamblee.usermgt.UserAdministrationImpl">
+
+ <constructor-arg>
+ <ref bean="org.wamblee.usermgt.UserSet"/>
+ </constructor-arg>
+
+ <constructor-arg>
+ <ref bean="org.wamblee.usermgt.GroupSet"/>
+ </constructor-arg>
+
+ <constructor-arg>
+ <bean class="org.wamblee.usermgt.RegexpNameValidator">
+ <constructor-arg><value>[a-zA-Z]+[a-zA-Z0-9]*</value></constructor-arg>
+ <constructor-arg><value>INVALID_USERNAME</value></constructor-arg>
+ <constructor-arg><value></value></constructor-arg>
+ </bean>
+ </constructor-arg>
+
+ <constructor-arg>
+ <bean class="org.wamblee.usermgt.RegexpNameValidator">
+ <constructor-arg><value>[a-zA-Z]+[a-zA-Z0-9]*</value></constructor-arg>
+ <constructor-arg><value>INVALID_GROUPNAME</value></constructor-arg>
+ <constructor-arg><value></value></constructor-arg>
+ </bean>
+ </constructor-arg>
+
+ </bean>
+
+ <bean id="usermanagement-lock" class="org.wamblee.concurrency.JvmLock"/>
+
+ <bean id="usermanagement-lock-advice" class="org.wamblee.concurrency.spring.LockAdvice">
+ <constructor-arg><ref bean="usermanagement-lock"/></constructor-arg>
+ </bean>
+
+ <bean id="org.wamblee.usermgt.UserAdministration"
+ class="org.springframework.aop.framework.ProxyFactoryBean">
+ <property name="proxyInterfaces"><value>org.wamblee.usermgt.UserAdministration</value></property>
+ <property name="interceptorNames"><value>usermanagement-lock-advice</value></property>
+ <property name="target"><ref bean="org.wamblee.usermgt.UserAdministration-target"/></property>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.DENIED;
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+import junit.framework.TestCase;
+
+import org.wamblee.usermgt.UserAccessor;
+
+/**
+ * Tests the authorization service.
+ *
+ * @author Erik Brakkee
+ */
+public class AuthorizationServiceTest extends TestCase {
+
+ private AuthorizationRule _rule1;
+ private AuthorizationRule _rule2;
+ private AuthorizationRule _rule3;
+ private AuthorizationService _service;
+
+ protected AuthorizationService getService() {
+ return _service;
+ }
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ _rule1 = createRule(GRANTED, "users", "/oni/", AllOperation.class);
+ _rule2 = createRule(DENIED, "users", "/abc/", ReadOperation.class);
+ _rule3 = createRule(GRANTED, "users", "/abc/", AllOperation.class);
+
+ _service = createService();
+ _service.appendRule(_rule1);
+ _service.appendRule(_rule2);
+ _service.appendRule(_rule3);
+ }
+
+ protected void resetTestRules() {
+ ((TestAuthorizationRule)_rule1).reset();
+ ((TestAuthorizationRule)_rule2).reset();
+ ((TestAuthorizationRule)_rule3).reset();
+ }
+
+ protected UserAccessor createUserAccessor() {
+ return new TestUserAccessor();
+ }
+
+ /**
+ * Creates an authorization service with some rules for testing. .
+ * @return Authorization service.
+ */
+ protected AuthorizationService createService() {
+ DefaultAuthorizationService service = new DefaultAuthorizationService() ;
+ service.setUserAccessor(createUserAccessor());
+ return service;
+ }
+
+ protected AuthorizationRule createRule(AuthorizationResult aResult, String aGroup, String aPath, Class<? extends Operation> aOperation) {
+ return new TestAuthorizationRule(aResult, aGroup, aPath, aOperation);
+ }
+
+ protected void checkMatchCount(int aCount, AuthorizationRule aRule) {
+ assertEquals( aCount, ((TestAuthorizationRule)aRule).getMatchCount());
+ }
+
+ protected Object createResource(String aPath) {
+ return new TestResource(aPath);
+ }
+
+ protected void checkRuleCount(int aCount) {
+ // Empty
+ }
+
+ /**
+ * Several checks to verify the outcome of matching against the first rule.
+ *
+ */
+ public void testFirstRuleGrants() {
+ assertTrue( _service.isAllowed(createResource("/oni/xyz.jpg"), new ReadOperation()));
+ checkMatchCount(1, _rule1);
+ assertTrue(_service.isAllowed(createResource("/oni/xyz.jpg"), new WriteOperation()));
+ checkMatchCount(2, _rule1);
+ assertTrue(_service.isAllowed(createResource("/oni/xyz.jpg"), new DeleteOperation()));
+ checkMatchCount(3, _rule1);
+ assertTrue(_service.isAllowed(createResource("/oni/xyz.jpg"), new CreateOperation()));
+ checkMatchCount(4, _rule1);
+ checkMatchCount(0, _rule2);
+ checkMatchCount(0, _rule3);
+ }
+
+ /**
+ * Verify that a match with the second rule leads to a denial of authorization.
+ *
+ */
+ public void testSecondRuleDenies() {
+ assertFalse(_service.isAllowed(createResource("/abc/xyz.jpg"), new ReadOperation()));
+ checkMatchCount(0, _rule1);
+ checkMatchCount(1, _rule2);
+ checkMatchCount(0, _rule3);
+ }
+
+ /**
+ * Verifies that the third rule is used when appropriate and that it grants access.
+ *
+ */
+ public void testThirdRuleGrants() {
+ assertTrue(_service.isAllowed(createResource("/abc/xyz.jpg"), new WriteOperation()));
+ checkMatchCount(0, _rule1);
+ checkMatchCount(0, _rule2);
+ checkMatchCount(1, _rule3);
+ }
+
+ /**
+ * Removes a rule and checks it is removed.
+ *
+ */
+ public void testRemoveRule() {
+ checkRuleCount(3);
+ assertTrue(_service.isAllowed(createResource("/abc/xyz.jpg"), new WriteOperation()));
+ _service.removeRule(2);
+ assertFalse(_service.isAllowed(createResource("/abc/xyz.jpg"), new WriteOperation()));
+ checkRuleCount(2);
+ }
+
+ /**
+ * Inserts a rule and checks it is inserted.
+ *
+ */
+ public void testInsertRule() {
+ checkRuleCount(3);
+ assertFalse(_service.isAllowed(createResource("/janse/xyz.jpg"), new WriteOperation()));
+ _service.appendRule(createRule(GRANTED, "users", "/janse/", WriteOperation.class));
+ assertTrue(_service.isAllowed(createResource("/janse/xyz.jpg"), new WriteOperation()));
+ checkRuleCount(4);
+
+ }
+
+ /**
+ * Gets the rules. Verifies that all rules are obtained.
+ *
+ */
+ public void testGetRules() {
+ AuthorizationRule[] rules = _service.getRules();
+ assertEquals(3, rules.length);
+ }
+
+ /**
+ * Verifies that when no rules match, access is denied.
+ *
+ */
+ public void testNoRulesSupportResource() {
+ assertFalse(_service.isAllowed(createResource("/xyxyxyxy"), new ReadOperation()));
+ checkMatchCount(0, _rule1);
+ checkMatchCount(0, _rule2);
+ checkMatchCount(0, _rule3);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+/**
+ * Test of the operation registry.
+ *
+ * @author Erik Brakkee
+ */
+public class DefaultOperationRegistryTest extends TestCase {
+
+ private OperationRegistry _registry;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+
+ _registry = new DefaultOperationRegistry(new Operation[] {
+ new AllOperation(),
+ new ReadOperation(),
+ new WriteOperation(),
+ new DeleteOperation(),
+ new CreateOperation()
+ });
+ }
+
+ /**
+ * Tests encoding and decoding of no operations.
+ *
+ */
+ public void testEncodeDecodeNooperations() {
+ assertEquals("", _registry.encode(new Operation[0]));
+ assertEquals(0, _registry.decode(Object.class, "").length);
+ }
+
+ /**
+ * Verifies that encoding of operations into a string works.
+ *
+ */
+ public void testEncode() {
+ assertEquals("read,write", _registry.encode(new Operation[] { new ReadOperation(), new WriteOperation() }));
+ }
+
+ /**
+ * Verifies that decoding of operation from a string works.
+ *
+ */
+ public void testDecode() {
+ Operation[] operations = _registry.decode(Object.class, "read,write");
+ assertTrue( operations[0] instanceof ReadOperation);
+ assertTrue( operations[1] instanceof WriteOperation);
+ }
+
+ /**
+ * Verifies that an IllegalArgumentException occurs when attempting to decode
+ * an operation that is not known.
+ *
+ */
+ public void testDecodeUnknownOperation() {
+ try {
+ _registry.decode(Object.class, "bla");
+ fail();
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for regular expression matching.
+ *
+ * @author Erik Brakkee
+ */
+public class RegexpPathConditionTest extends TestCase {
+
+ /**
+ * Various tests.
+ *
+ */
+ public void testMatch() {
+ PathCondition cond = new RegexpPathCondition("abc");
+ assertTrue(cond.matches("abc"));
+ assertFalse(cond.matches("xabcx"));
+ cond = new RegexpPathCondition("/[a-z]*/.*");
+ assertTrue(cond.matches("/hallo/xyz"));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for StartsWithPathCondition.
+ *
+ * @author Erik Brakkee
+ */
+public class StartsWithPathConditionTest extends TestCase {
+
+ /**
+ * Various tests.
+ */
+ public void testMatches() {
+ PathCondition cond = new StartsWithPathCondition("/hallo");
+ assertTrue(cond.matches("/hallo"));
+ assertTrue(cond.matches("/hallox"));
+ assertTrue(cond.matches("/hallo/abc"));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.DENIED;
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+
+import org.wamblee.usermgt.User;
+
+/**
+ * Test authorization rule that also counts the number of times the rule matches.
+ *
+ * @author Erik Brakkee
+ */
+public class TestAuthorizationRule extends UrlAuthorizationRule {
+
+ /**
+ * Counts the number of matches.
+ */
+ private int _matches = 0;
+
+ public TestAuthorizationRule( AuthorizationResult aResult, String aGroup,
+ String aPath, Class<? extends Operation> aOperation) {
+ super(aResult, new GroupUserCondition(aGroup),
+ new StartsWithPathCondition(aPath), TestResource.class, new IsaOperationCondition(aOperation));
+ }
+
+ protected TestAuthorizationRule() {
+ super();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.UrlAuthorizationRule#getPath(java.lang.Object)
+ */
+ @Override
+ protected String getResourcePath(Object aResource) {
+ return ((TestResource)aResource).getPath();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.security.authorization.UrlAuthorizationRule#isAllowed(java.lang.Object, org.wamblee.security.authorization.Operation, org.wamblee.usermgt.UserAccessor)
+ */
+ @Override
+ public AuthorizationResult isAllowed(Object aResource, Operation anOperation, User aUser) {
+
+ AuthorizationResult result = super.isAllowed(aResource, anOperation, aUser);
+ if ( result.equals(GRANTED) || result.equals(DENIED)) {
+ _matches++;
+ }
+ return result;
+ }
+
+ public int getMatchCount() {
+ return _matches;
+ }
+
+ public void reset() {
+ _matches = 0;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+/**
+ * A test resource for authorization.
+ *
+ * @author Erik Brakkee
+ */
+public class TestResource {
+
+ private String _path;
+
+ public TestResource(String aPath) {
+ _path = aPath;
+ }
+
+ public String getPath() {
+ return _path;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import junit.framework.TestCase;
+
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.InMemoryGroupSet;
+import org.wamblee.usermgt.InMemoryUserSet;
+import org.wamblee.usermgt.RegexpNameValidator;
+import org.wamblee.usermgt.User;
+import org.wamblee.usermgt.UserAccessor;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserAdministrationImpl;
+import org.wamblee.usermgt.UserMgtException;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * User access that always returns a user that belongs to
+ * a fixed group.
+ *
+ * @author Erik Brakkee
+ */
+public class TestUserAccessor implements UserAccessor {
+
+ private static final String USER = "erik";
+ private static final String PASSWORD = "abc123";
+ private static final String GROUP = "users";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAccessor#getCurrentUser()
+ */
+ public User getCurrentUser() {
+ UserAdministration admin = new UserAdministrationImpl(
+ new InMemoryUserSet( new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, Reason.INVALID_PASSWORD, "Password must contain at least 6 characters"),
+ new Md5HexMessageDigester()), new InMemoryGroupSet(),
+ new RegexpNameValidator(RegexpNameValidator.ID_PATTERN, Reason.INVALID_USERNAME, "Invalid user"),
+ new RegexpNameValidator(RegexpNameValidator.ID_PATTERN, Reason.INVALID_GROUPNAME, "Invalid group")
+ );
+ try {
+ Group group = admin.createGroup(GROUP);
+ return admin.createUser(USER, PASSWORD, group);
+ } catch (UserMgtException e) {
+ TestCase.fail(e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization;
+
+import static org.wamblee.security.authorization.AuthorizationResult.GRANTED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNDECIDED;
+import static org.wamblee.security.authorization.AuthorizationResult.UNSUPPORTED_RESOURCE;
+import junit.framework.TestCase;
+
+import org.wamblee.usermgt.User;
+
+
+/**
+ * Tests for the {@link org.wamblee.security.authorization.UrlAuthorizationRule}.
+ *
+ * @author Erik Brakkee
+ */
+public class UrlAuthorizationRuleTest extends TestCase {
+
+ /**
+ * Constructs the rule with a result of UNDECIDED. Verifies that an IllegalArgumentException
+ * is thrown.
+ *
+ */
+ public void testConstructWithUndecidedResult() {
+ try {
+ new TestAuthorizationRule(UNDECIDED, "users", "/path", ReadOperation.class);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+
+ /**
+ * Constructs the rule with a result of UNSUPPORTED_RESOURCE. Verifies that an IllegalArgumentException
+ * is thrown.
+ *
+ */
+ public void testConstructWithUnsupportedResult() {
+ try {
+ new TestAuthorizationRule(UNSUPPORTED_RESOURCE, "users", "/path", ReadOperation.class);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+
+ /**
+ * Constructs the authorization rule and applies it to an unsupported object type.
+ * Verifies that the result is UNSUPPORTED_RESOURCE.
+ *
+ */
+ public void testUnsupportedObject() {
+ AuthorizationRule rule = new TestAuthorizationRule(GRANTED, "users", "/path", ReadOperation.class);
+ assertEquals(UNSUPPORTED_RESOURCE, rule.isAllowed("hello", new ReadOperation(), new TestUserAccessor().getCurrentUser()));
+ }
+
+ public void testMatchingScenarios() {
+ AuthorizationRule rule = new TestAuthorizationRule(GRANTED, "users", "/path/", ReadOperation.class);
+ User user = new TestUserAccessor().getCurrentUser();
+
+ // everything matches
+ assertEquals(GRANTED, rule.isAllowed(new TestResource("/path/a"), new ReadOperation(), user));
+ assertEquals(GRANTED, rule.isAllowed(new TestResource("/path/"), new ReadOperation(), user));
+
+ // path does not match.
+ assertEquals(UNDECIDED, rule.isAllowed(new TestResource("/path"), new ReadOperation(), user));
+
+ // operation does not match.
+ assertEquals(UNDECIDED, rule.isAllowed(new TestResource("/path/"), new WriteOperation(), user));
+
+ // group does not match.
+ AuthorizationRule rule2 = new TestAuthorizationRule(GRANTED, "users2", "/path/", ReadOperation.class);
+ assertEquals(UNDECIDED, rule2.isAllowed(new TestResource("/path/a"), new ReadOperation(), user));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.authorization.hibernate;
+
+import java.sql.SQLException;
+
+import org.apache.log4j.Logger;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.dialect.MySQL5Dialect;
+import org.hibernate.dialect.MySQL5InnoDBDialect;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.security.authorization.AuthorizationService;
+import org.wamblee.security.authorization.AuthorizationServiceTest;
+import org.wamblee.security.authorization.TestUserAccessor;
+import org.wamblee.system.adapters.ClassConfiguration;
+import org.wamblee.system.adapters.ClassConfigurationTest;
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.components.DatabaseComponentFactory;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.component.DatabaseTesterComponent;
+import org.wamblee.system.spring.component.DatasourceComponent;
+import org.wamblee.usermgt.UserAccessor;
+import org.wamblee.usermgt.hibernate.AuthorizationComponent;
+import org.wamblee.usermgt.hibernate.HibernateUserAdministrationTest;
+import org.wamblee.usermgt.hibernate.UserAdministrationComponent;
+
+/**
+ * Unit test for the persistent authorization service.
+ *
+ * @author Erik Brakkee
+ */
+public class PersistentAuthorizationServiceTest extends
+ AuthorizationServiceTest {
+
+ private static final Logger LOGGER = Logger
+ .getLogger(PersistentAuthorizationServiceTest.class);
+
+ private static final String SERVICE_TABLE = "AUTHORIZATION_SERVICE";
+ private static final String RULES_TABLE = "AUTHORIZATION_RULES";
+ private static final String SERVICE_RULES_TABLE = "AUTHORIZATION_SERVICE_RULES";
+ private static final String OPERATIONCOND_TABLE = "OPERATION_CONDITIONS";
+ private static final String PATHCOND_TABLE = "PATH_CONDITIONS";
+ private static final String USERCOND_TABLE = "USER_CONDITIONS";
+
+ private DefaultContainer _container;
+ private Scope _scope;
+
+ private DatabaseTesterComponent _databaseTester;
+ private UserAccessor _userAccessor;
+ private HibernateTemplate _hibernateTemplate;
+ private AuthorizationService _authorizationService;
+
+ @Override
+ protected void setUp() throws Exception {
+
+ _container = new DefaultContainer("top");
+ DatabaseComponentFactory.addDatabaseConfig(_container);
+ _container.addComponent(new DatasourceComponent("datasource"));
+ ClassConfiguration useraccessorConfig = new ClassConfiguration(
+ TestUserAccessor.class);
+ useraccessorConfig.getObjectConfig().getSetterConfig().initAllSetters();
+ _container.addComponent("userAccessor", useraccessorConfig);
+ _container.addComponent(new AuthorizationComponent("authorization",
+ true));
+
+ ClassConfiguration dbtesterConfig = new ClassConfiguration(
+ DatabaseTesterComponent.class);
+ dbtesterConfig.getObjectConfig().getSetterConfig().initAllSetters();
+ _container.addComponent("databaseTester", dbtesterConfig);
+
+ ObjectConfiguration config = new ObjectConfiguration(
+ PersistentAuthorizationServiceTest.class);
+ config.getSetterConfig().clear().add("setUserAccessor").add(
+ "setDatabaseTester").add("setHibernateTemplate").add(
+ "setAuthorizationService");
+ _container.addComponent("testcase", this, config);
+
+ _scope = _container.start();
+
+ _databaseTester.cleanDatabase();
+
+ super.setUp();
+ }
+
+ public void setDatabaseTester(DatabaseTesterComponent aDatabaseTester) {
+ _databaseTester = aDatabaseTester;
+ }
+
+ public void setUserAccessor(UserAccessor aUserAccessor) {
+ _userAccessor = aUserAccessor;
+ }
+
+ public void setHibernateTemplate(HibernateTemplate aHibernateTemplate) {
+ _hibernateTemplate = aHibernateTemplate;
+ }
+
+ public void setAuthorizationService(
+ AuthorizationService aAuthorizationService) {
+ _authorizationService = aAuthorizationService;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.wamblee.security.authorization.AuthorizationServiceTest#createService
+ * ()
+ */
+ @Override
+ protected AuthorizationService createService() {
+ PersistentAuthorizationService service = new PersistentAuthorizationService(
+ "DEFAULT", _hibernateTemplate, createUserAccessor(), 10000);
+ return service;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.wamblee.security.authorization.AuthorizationServiceTest#checkRuleCount
+ * (int)
+ */
+ @Override
+ protected void checkRuleCount(int aCount) {
+ try {
+ assertEquals(1, _databaseTester.getTableSize(SERVICE_TABLE));
+ assertEquals(aCount, _databaseTester.getTableSize(RULES_TABLE));
+ assertEquals(aCount, _databaseTester
+ .getTableSize(SERVICE_RULES_TABLE));
+ assertEquals(aCount, _databaseTester.getTableSize(USERCOND_TABLE));
+ assertEquals(aCount, _databaseTester.getTableSize(PATHCOND_TABLE));
+ assertEquals(aCount, _databaseTester
+ .getTableSize(OPERATIONCOND_TABLE));
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void testSchemaExport() {
+ Configuration config = new Configuration();
+ for (String mappingFile: new AuthorizationMappingFiles()) {
+ config.addResource(mappingFile);
+ }
+ config.setProperty("hibernate.dialect", MySQL5InnoDBDialect.class.getName());
+ SchemaExport exporter = new SchemaExport(config);
+ exporter.setOutputFile("target/mysql5.schema.sql");
+ exporter.create(true,false);
+ }
+
+ public void testPerformance() {
+
+ PersistentAuthorizationService service = (PersistentAuthorizationService) getService();
+
+ int n = 1000;
+ long time = System.currentTimeMillis();
+ for (int i = 0; i < n; i++) {
+ testFirstRuleGrants();
+ resetTestRules();
+ testSecondRuleDenies();
+ resetTestRules();
+ testThirdRuleGrants();
+ resetTestRules();
+ testNoRulesSupportResource();
+ }
+ LOGGER.info("Executed " + 4 * n + " authorization checks in "
+ + (float) (System.currentTimeMillis() - time) / (float) 1000
+ + " seconds.");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.security.encryption;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the message digester.
+ *
+ * @author Erik Brakkee
+ */
+public class MessageDigesterTest extends TestCase {
+
+ public void testMd5HexEncoding() {
+ assertEquals("e99a18c428cb38d5f260853678922e03", new Md5HexMessageDigester().hash("abc123"));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.sql.SQLException;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the inmemory group set. Intended to be subclassed for other
+ * implementations of group set.
+ */
+public class InMemoryGroupSetTest extends TestCase {
+
+ protected GroupSet _groups;
+
+ /**
+ * This method must be overriden in subclasses.
+ * @return New group set object.
+ */
+ protected GroupSet createGroupSet() {
+ return new InMemoryGroupSet();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.test.SpringTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _groups = createGroupSet();
+ checkGroupCount(0);
+ }
+
+
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aGroup Group to check for existence.
+ */
+ protected void checkGroupExists(String aGroup) throws SQLException {
+ // Empty
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aGroup Group to check for non-existence.
+ */
+ protected void checkGroupNotExists(String aGroup) throws SQLException {
+ // Empty
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aSize Expected number of groups.
+ */
+ protected void checkGroupCount(int aSize) throws SQLException {
+ assertEquals(aSize, _groups.size());
+ }
+
+ /**
+ * Adds a group and verifies that the group is added using
+ * find(), list(), and contains().
+ *
+ */
+ public void testAdd() throws SQLException {
+ Group group = new Group("group1");
+ assertTrue( _groups.add(group) );
+ checkGroupExists(group.getName());
+ checkGroupCount(1);
+ Group group2 = _groups.find("group1");
+ assertNotNull(group2);
+ assertEquals(group.getName(), group2.getName());
+ Set<Group> set = _groups.list();
+ assertEquals(1, set.size());
+ assertTrue(set.contains(group));
+ }
+
+ /**
+ * Tries to find a non-existing group. Verifies that null is
+ * returned.
+ *
+ */
+ public void testFindUnknownGroup() throws SQLException {
+ Group group1 = new Group("group1");
+ Group group2 = new Group("group2");
+ _groups.add(group1);
+ _groups.add(group2);
+ checkGroupExists(group1.getName());
+ checkGroupExists(group2.getName());
+
+ assertNull( _groups.find("group3") );
+ checkGroupNotExists("group3");
+ }
+
+ /**
+ * Adds duplicate group. Verifies that the existing group is left untouched.
+ */
+ public void testAddDuplicateGroup() throws SQLException {
+ Group group1 = new Group("group1");
+ _groups.add(group1);
+
+ assertEquals(1, _groups.list().size());
+ assertTrue(_groups.contains(group1));
+ group1 = new Group("group1");
+ assertFalse(_groups.add(group1));
+ assertEquals(1, _groups.list().size());
+
+ checkGroupExists(group1.getName());
+ checkGroupCount(1);
+ }
+
+ /**
+ * Removes a group. Verifies that the group is
+ * removed and the return value is true.
+ *
+ */
+ public void testRemoveGroup() throws SQLException {
+ Group group1 = new Group("group1");
+ _groups.add(group1);
+ assertTrue(_groups.contains(group1));
+ checkGroupCount(1);
+
+ assertTrue(_groups.remove(group1));
+ assertFalse(_groups.contains(group1));
+ assertNull(_groups.find(group1.getName()));
+ assertEquals(0, _groups.list().size());
+ checkGroupCount(0);
+ }
+
+ /**
+ * Removes a non-existing group. Verifies that no groups are
+ * removed an that the return value is true.
+ *
+ */
+ public void testRemoveNonExistingGroup() throws SQLException {
+ Group group1 = new Group("group1");
+ _groups.add(group1);
+ checkGroupCount(1);
+ Group nonExistingGroup = new Group("group2");
+ nonExistingGroup.setPrimaryKey(new Long(1000));
+ nonExistingGroup.setPersistedVersion(1000);
+ assertFalse(_groups.remove(nonExistingGroup));
+ assertTrue(_groups.contains(group1));
+ assertEquals(1, _groups.list().size());
+ checkGroupCount(1);
+ }
+
+ /**
+ * Adds a number of groups to the set and verifies that list()
+ * returns them all.
+ *
+ */
+ public void testList() throws SQLException {
+ Group group1 = new Group("group1");
+ Group group2 = new Group("group2");
+ Group group3 = new Group("group3");
+ assertTrue(_groups.add(group1));
+ assertTrue(_groups.add(group2));
+ assertTrue(_groups.add(group3));
+
+ checkGroupExists(group1.getName());
+ checkGroupExists(group2.getName());
+ checkGroupExists(group3.getName());
+
+ Set<Group> set = _groups.list();
+ assertTrue(set.contains(group1));
+ assertTrue(set.contains(group2));
+ assertTrue(set.contains(group3));
+
+ checkGroupCount(3);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.sql.SQLException;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Tests the inmemory user set. Intended to be subclassed for other
+ * implementations of user set.
+ */
+public class InMemoryUserSetTest extends TestCase {
+
+ protected static final String PASSWORD = "abc123";
+
+ private UserSet _users;
+ private GroupSet _groups;
+
+ private Group _group;
+
+ /**
+ * This method must be overriden in subclasses.
+ * @return New user set object.
+ */
+ protected UserSet createUserSet() {
+ return new InMemoryUserSet( new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, Reason.INVALID_PASSWORD, "Password must contain at least 6 characters"),
+ new Md5HexMessageDigester());
+ }
+
+ /**
+ * This method must be overriden in subclasses.
+ * @return New group set object.
+ */
+ protected GroupSet createGroupSet() {
+ return new InMemoryGroupSet();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.test.SpringTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _users = createUserSet();
+ _groups = createGroupSet();
+ _group = new Group("group0");
+ _groups.add(_group);
+ checkUserCount(0);
+
+ }
+
+ protected UserSet getUsers() {
+ return _users;
+ }
+
+ protected GroupSet getGroups() {
+ return _groups;
+ }
+
+ protected Group createGroup(String aName) {
+ return new Group(aName);
+ }
+
+ protected User createUser(String aName, String aPassword, Group aGroup) throws UserMgtException {
+ return UsermgtTestUtils.createUser(aName, aPassword, aGroup);
+ }
+
+ protected void addUserToGroup(User aUser, Group aGroup) throws UserMgtException {
+ aUser.addGroup(aGroup);
+ }
+
+ protected void removeUserFromGroup(User aUser, Group aGroup ) throws UserMgtException {
+ aUser.removeGroup(aGroup);
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aUser User to check for existence.
+ */
+ protected void checkUserExists(String aUser) throws SQLException {
+ // Empty
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aUser User to check for non-existence.
+ */
+ protected void checkUserNotExists(String aUser) throws SQLException {
+ // Empty
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aSize Expected number of users.
+ */
+ protected void checkUserCount(int aSize) throws SQLException {
+ assertEquals(aSize, _users.size());
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aUser User to check for existence.
+ */
+ protected void checkGroupExists(String aUser) throws SQLException {
+ // Empty
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aUser User to check for non-existence.
+ */
+ protected void checkGroupNotExists(String aUser) throws SQLException {
+ // Empty
+ }
+
+ /**
+ * Additional check to be implemented by a subclass.
+ * @param aSize Expected number of users.
+ */
+ protected void checkGroupCount(int aSize) throws SQLException {
+ // Empty
+ }
+
+
+ /**
+ * Adds a user and verifies that the user is added using
+ * find(), list(), and contains().
+ *
+ */
+ public void testAdd() throws SQLException, UserMgtException {
+ User user = createUser("user1", PASSWORD, _group);
+ assertTrue( _users.add(user) );
+ checkUserExists(user.getName());
+ checkUserCount(1);
+ User user2 = _users.find("user1");
+ assertNotNull(user2);
+ assertEquals(user.getName(), user2.getName());
+ Set<User> set = _users.list();
+ assertEquals(1, set.size());
+ assertTrue(set.contains(user));
+ }
+
+ /**
+ * Tries to find a non-existing user. Verifies that null is
+ * returned.
+ *
+ */
+ public void testFindUnknownUser() throws SQLException, UserMgtException {
+ User user1 = createUser("user1", PASSWORD, _group);
+ User user2 = createUser("user2", PASSWORD, _group);
+ _users.add(user1);
+ _users.add(user2);
+ checkUserExists(user1.getName());
+ checkUserExists(user2.getName());
+
+ assertNull( _users.find("user3") );
+ checkUserNotExists("user3");
+ }
+
+ /**
+ * Adds duplicate user. Verifies that the existing user is left untouched.
+ */
+ public void testAddDuplicateUser() throws SQLException, UserMgtException {
+ User user1 = createUser("user1", PASSWORD, _group);
+ _users.add(user1);
+
+ assertEquals(1, _users.list().size());
+ assertTrue(_users.contains(user1));
+ user1 = createUser("user1", PASSWORD, _group);
+ assertFalse(_users.add(user1));
+ assertEquals(1, _users.list().size());
+
+ checkUserExists(user1.getName());
+ checkUserCount(1);
+ }
+
+ /**
+ * Removes a user. Verifies that the user is
+ * removed and the return value is true.
+ *
+ */
+ public void testRemoveUser() throws SQLException, UserMgtException {
+ User user1 = createUser("user1", PASSWORD, _group);
+ _users.add(user1);
+ assertTrue(_users.contains(user1));
+ checkUserCount(1);
+
+ assertTrue(_users.remove(user1));
+ assertFalse(_users.contains(user1));
+ assertNull(_users.find(user1.getName()));
+ assertEquals(0, _users.list().size());
+ checkUserCount(0);
+ }
+
+ /**
+ * Removes a non-existing user. Verifies that no users are
+ * removed an that the return value is true.
+ *
+ */
+ public void testRemoveNonExistingUser() throws SQLException, UserMgtException {
+ User user1 = createUser("user1", PASSWORD, _group);
+ _users.add(user1);
+ checkUserCount(1);
+ User nonExistingUser = createUser("user2", PASSWORD, _group);
+ nonExistingUser.setPrimaryKey(new Long(1000));
+ nonExistingUser.setPersistedVersion(10);
+ assertFalse(_users.remove(nonExistingUser));
+ assertTrue(_users.contains(user1));
+ assertEquals(1, _users.list().size());
+ checkUserCount(1);
+ }
+
+ /**
+ * Adds a number of users to the set and verifies that list()
+ * returns them all.
+ *
+ */
+ public void testList() throws SQLException, UserMgtException {
+ User user1 = createUser("user1", PASSWORD, _group);
+ User user2 = createUser("user2", PASSWORD, _group);
+ User user3 = createUser("user3", PASSWORD, _group);
+ assertTrue(_users.add(user1));
+ assertTrue(_users.add(user2));
+ assertTrue(_users.add(user3));
+
+ checkUserExists(user1.getName());
+ checkUserExists(user2.getName());
+ checkUserExists(user3.getName());
+
+ Set<User> set = _users.list();
+ assertTrue(set.contains(user1));
+ assertTrue(set.contains(user2));
+ assertTrue(set.contains(user3));
+
+ checkUserCount(3);
+ }
+
+ /**
+ * Adds several users to different groups and verifies that
+ * the correct users are returned when looking for users in
+ * different groups.
+ * @throws SQLException
+ */
+ public void testListByGroup() throws SQLException, UserMgtException {
+ Group group1 = new Group("group1");
+ Group group2 = new Group("group2");
+ Group group3 = new Group("group3");
+ _groups.add(group1);
+ _groups.add(group2);
+ _groups.add(group3);
+
+ // user1 user2 user3
+ // group1 y
+ // group2 y y
+ // group3 y y y
+
+ User user1 = createUser("user1", PASSWORD, group1);
+ user1.addGroup(group2);
+ user1.addGroup(group3);
+ User user2 = createUser("user2", PASSWORD, group2);
+ user2.addGroup(group3);
+ User user3 = createUser("user3", PASSWORD, group3);
+ _users.add(user1);
+ _users.add(user2);
+ _users.add(user3);
+
+ checkUserExists(user1.getName());
+ checkUserExists(user2.getName());
+ checkUserExists(user3.getName());
+
+ checkGroupExists(group1.getName());
+ checkGroupExists(group2.getName());
+ checkGroupExists(group3.getName());
+
+ checkUserCount(3);
+ checkGroupCount(3+1); // also count the group that was created in the setUp().
+
+ Set<User> list = _users.list(group1);
+ assertTrue(list.contains(user1));
+ assertEquals(1, list.size());
+
+ list = _users.list(group2);
+ assertTrue(list.contains(user1));
+ assertTrue(list.contains(user2));
+ assertEquals(2, list.size());
+
+ list = _users.list(group3);
+ assertTrue(list.contains(user1));
+ assertTrue(list.contains(user2));
+ assertTrue(list.contains(user3));
+ assertEquals(3, list.size());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * Test of user administration implementation.
+ *
+ * @author Erik Brakkee
+ */
+public class UserAdministrationImplTest extends TestCase {
+
+ private static final Logger LOGGER = Logger
+ .getLogger(UserAdministrationImplTest.class);
+
+ private static final String USER1 = "piet";
+
+ private static final String PASS1 = "passpiet";
+
+ private static final String USER2 = "kees";
+
+ private static final String PASS2 = "passkees";
+
+ private static final String GROUP1 = "cyclists";
+
+ private static final String GROUP2 = "runners";
+
+ private UserAdministration _admin;
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _admin = createAdmin();
+ }
+
+ protected UserAdministration createAdmin() {
+ UserSet users = new InMemoryUserSet( new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN, Reason.INVALID_PASSWORD, "Password must contain at least 6 characters"),
+ new Md5HexMessageDigester());
+ GroupSet groups = new InMemoryGroupSet();
+ return new UserAdministrationImpl(users, groups,
+ new RegexpNameValidator(RegexpNameValidator.ID_PATTERN,
+ Reason.INVALID_USERNAME, "Invalid user"),
+ new RegexpNameValidator(RegexpNameValidator.ID_PATTERN,
+ Reason.INVALID_GROUPNAME, "Invalid group"));
+ }
+
+ protected User createUser(String aName, String aPassword, Group aGroup) throws UserMgtException {
+ return UsermgtTestUtils.createUser(aName, aPassword, aGroup);
+ }
+
+ /**
+ * Constructs the admin, verify it contains no users and no groups.
+ */
+ public void testConstruct() {
+ assertEquals(0, _admin.getUsers().size());
+ assertEquals(0, _admin.getGroups().size());
+ assertEquals(0, _admin.getUserCount());
+ assertEquals(0, _admin.getGroupCount());
+ }
+
+ /**
+ * Creates a new group. Verifies the group is created correctly and that the
+ * user is added.
+ *
+ */
+ public void testCreateGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ assertNotNull(group);
+ assertEquals(GROUP1, group.getName());
+
+ Set<Group> groups = _admin.getGroups();
+ assertEquals(1, groups.size());
+ assertEquals(1, _admin.getGroupCount());
+ assertTrue(groups.contains(group));
+ }
+
+ private void createInvalidGroup(String aUsername) {
+ try {
+ _admin.createGroup(aUsername);
+ fail();
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.INVALID_GROUPNAME, e
+ .getReason());
+ assertEquals(0, _admin.getGroupCount());
+ }
+ }
+
+ /**
+ * Creates a new group with an invalid name. Verifies that the appropriate
+ * exception is thrown.
+ *
+ * @throws UserMgtException
+ */
+ public void testCreateInvalidGroupName() throws UserMgtException {
+ createInvalidGroup("");
+ createInvalidGroup("0abc"); // should not start with digits
+ createInvalidGroup("a b"); // should not contain spaces
+ createInvalidGroup(" aa");
+ createInvalidGroup("aa ");
+ }
+
+ /**
+ * Creates a new group which conflicts with an existing one. Verifies that
+ * the UserMgtException is thrown and that no group is added.
+ *
+ */
+ public void testCreateDuplicateGroup() throws UserMgtException {
+ _admin.createGroup(GROUP1);
+ try {
+ _admin.createGroup(GROUP1);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.DUPLICATE_GROUP, e.getReason());
+ assertEquals(1, _admin.getGroupCount());
+ return;
+ }
+ fail();
+ }
+
+ /**
+ * Creates a new user. Verifies the user is created correctly and that the
+ * user is added.
+ *
+ */
+ public void testCreateUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ assertNotNull(user);
+ assertEquals(USER1, user.getName());
+ user.checkPassword(PASS1);
+
+ Set<User> users = _admin.getUsers();
+ assertEquals(1, users.size());
+ assertEquals(1, _admin.getUserCount());
+ assertTrue(users.contains(user));
+ }
+
+ private void createInvalidUser(String aUsername, Group aGroup) {
+ try {
+ _admin.createUser(aUsername, "pass", aGroup);
+ fail();
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.INVALID_USERNAME, e
+ .getReason());
+ assertEquals(0, _admin.getUserCount());
+ }
+ }
+
+ /**
+ * Constructs users with invalid names. Verifies that the appropriate
+ * exception is thrown.
+ *
+ */
+ public void testCreateInvalidUserName() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ createInvalidUser("", group);
+ createInvalidUser("0abc", group); // should not start with digits
+ createInvalidUser("a b", group); // should not contain spaces
+ createInvalidUser(" aa", group);
+ createInvalidUser("aa ", group);
+ }
+
+ /**
+ * Creates a new user which conflicts with an existing one. Verifies that
+ * the UserMgtException is thrown and that no user is added.
+ *
+ */
+ public void testCreateDuplicateUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ _admin.createUser(USER1, PASS1, group);
+ try {
+ _admin.createUser(USER1, PASS2, group);
+ fail();
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.DUPLICATE_USER, e.getReason());
+ assertEquals(1, _admin.getUserCount());
+ }
+ }
+
+ /**
+ * Gets a known user by name. Verifies the correct user is obtained.
+ * Verifies that null is returned when trying to obtain an unknown user.
+ *
+ */
+ public void testGetUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ User user2 = _admin.getUser(USER1);
+ assertTrue(user.equals(user2));
+ assertNull(_admin.getUser(USER2));
+ }
+
+ /**
+ * Gets a known group by name. Verifies the correct group is obtained.
+ * Verifies that null is returned when the group is not known.
+ *
+ */
+ public void testGetGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ Group group2 = _admin.getGroup(GROUP1);
+ assertTrue(group.equals(group2));
+ assertNull(_admin.getGroup(GROUP2));
+ }
+
+ /**
+ * Adds a user to a group. Verifies that the user is added using several API
+ * calls. Verifies that an exception occurs if the user is not already part
+ * of the group.
+ *
+ */
+ public void testAddUserToGroup() throws UserMgtException {
+
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ Group group2 = _admin.createGroup(GROUP2);
+ assertTrue(user.isInGroup(group));
+ assertFalse(user.isInGroup(group2));
+ _admin.addUserToGroup(user, group2);
+ assertTrue(user.isInGroup(group));
+ assertTrue(user.isInGroup(group2));
+ Set<User> users = _admin.getUsers(group2);
+ assertNotNull(users);
+ assertEquals(1, users.size());
+ assertTrue(users.contains(user));
+
+ try {
+ _admin.addUserToGroup(user, group);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.USER_ALREADY_IN_GROUP, e
+ .getReason());
+ return;
+ }
+ fail();
+ }
+
+ /**
+ * Adds a user to a group where the user does not exist. Verifies that an
+ * exception occurs.
+ *
+ */
+ public void testAddUserToGroupUnknownUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = createUser(USER1, PASS1, group);
+ try {
+ _admin.addUserToGroup(user, group);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.UNKNOWN_USER, e.getReason());
+ return;
+ }
+ fail();
+ }
+
+ /**
+ * Adds a user to a group where the user does not exist. Verifies that an
+ * exception occurs.
+ *
+ */
+ public void testAddUserToGroupUnknownGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ Group group2 = new Group(GROUP2);
+ try {
+ _admin.addUserToGroup(user, group2);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.UNKNOWN_GROUP, e.getReason());
+ return;
+ }
+ fail();
+ }
+
+ /**
+ * Removes a user from a group. Verifies that the user is removed from the
+ * group using several API calls. Verifies that an exception occurs if the
+ * user not part of the group or if the user is only part of one group.
+ */
+ public void testRemoveUserFromGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+
+ User user = _admin.createUser(USER1, PASS1, group);
+ Group group2 = _admin.createGroup(GROUP2);
+ _admin.addUserToGroup(user, group2);
+ Set<Group> groups = user.getGroups();
+ assertEquals(2, groups.size());
+ assertTrue(groups.contains(group));
+ assertTrue(groups.contains(group2));
+
+ _admin.removeUserFromGroup(user, group);
+ groups = user.getGroups();
+ assertEquals(1, groups.size());
+ assertTrue(groups.contains(group2));
+ assertFalse(groups.contains(group));
+ }
+
+ /**
+ * Removes a user from a group where the user is not known. Verifies that an
+ * exception is thrown.
+ *
+ */
+ public void testRemoveUserFromGroupUnknownUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = createUser(USER1, GROUP1, group);
+ try {
+ _admin.removeUserFromGroup(user, group);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.UNKNOWN_USER, e.getReason());
+ }
+ }
+
+ /**
+ * Removes a user from a group where the group is not known. Verifies that
+ * an exception is thrown.
+ *
+ */
+ public void testRemoveUserFromGroupUnknownGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ Group group2 = new Group(GROUP2);
+ try {
+ _admin.removeUserFromGroup(user, group2);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.UNKNOWN_GROUP, e.getReason());
+ }
+ }
+
+ /**
+ * Removes a user from a group where the user is only part of one group.
+ * Verifies that an exception is thrown.
+ */
+ public void testRemoveUserFromGroupOnlyGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ try {
+ _admin.removeUserFromGroup(user, group);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.USER_MUST_BE_IN_A_GROUP, e
+ .getReason());
+ }
+ }
+
+ /**
+ * Gets the list of users and groups. Verifies that the correct suers and
+ * groups are returned. Verifies also that the relations from user to group
+ * are correct.
+ *
+ */
+ public void testGetUsersAndGroups() throws UserMgtException {
+ Group group1 = _admin.createGroup(GROUP1);
+ Group group2 = _admin.createGroup(GROUP2);
+
+ User user1 = _admin.createUser(USER1, PASS1, group1);
+ _admin.addUserToGroup(user1, group2);
+ User user2 = _admin.createUser(USER2, PASS2, group2);
+
+ Set<User> users = _admin.getUsers();
+ assertEquals(2, users.size());
+ assertTrue(users.contains(user1));
+ assertTrue(users.contains(user2));
+
+ Set<Group> groups = _admin.getGroups();
+ assertEquals(2, groups.size());
+ assertTrue(groups.contains(group1));
+ assertTrue(groups.contains(group2));
+
+ assertTrue(user1.isInGroup(group1));
+ assertTrue(user1.isInGroup(group2));
+ assertFalse(user2.isInGroup(group1));
+ assertTrue(user2.isInGroup(group2));
+
+ Set<Group> groups1 = user1.getGroups();
+ assertEquals(2, groups1.size());
+
+ Set<Group> groups2 = user2.getGroups();
+ assertEquals(1, groups2.size());
+ }
+
+ /**
+ * Renames a user. Verifies that the user is renamed. Verifies that
+ * exceptions are thrown when an attempt is made to rename the user to
+ * itself or to another existing user, or when the group does not exist.
+ *
+ */
+ public void testRenameUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user1 = _admin.createUser(USER1, PASS1, group);
+ _admin.renameUser(user1, USER2);
+ assertEquals(USER2, user1.getName());
+ assertEquals(user1, _admin.getUser(USER2));
+
+ _admin.createUser(USER1, PASS1, group);
+
+ try {
+ _admin.renameUser(user1, USER1);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.DUPLICATE_USER, e.getReason());
+
+ // do a trivial reanem
+ try {
+ _admin.renameUser(user1, user1.getName());
+ } catch (UserMgtException e2) {
+ assertEquals(UserMgtException.Reason.TRIVIAL_RENAME, e2
+ .getReason());
+ return;
+ }
+ fail();
+ }
+ fail();
+ }
+
+ /**
+ * Renames a user to a user with an invalid username. Verifies that the
+ * appropriate exception is thrown.
+ *
+ */
+ public void testRenameUserInvalidUsername() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user1 = _admin.createUser(USER1, PASS1, group);
+ try {
+ _admin.renameUser(user1, USER2);
+ } catch (UserMgtException e) {
+ assertEquals(e.getReason(), Reason.INVALID_USERNAME);
+ }
+ }
+
+ /**
+ * Renames a group. Verifies that the group is renamed. Verifies that
+ * exceptions are thrown when an attempt is made to rename the group to
+ * itself or to another existing group or when the group does not exist.
+ *
+ */
+ public void testRenameGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ _admin.renameGroup(group, GROUP2);
+ assertEquals(GROUP2, group.getName());
+ assertEquals(group, _admin.getGroup(GROUP2));
+
+ _admin.createGroup(GROUP1);
+ try {
+ _admin.renameGroup(group, GROUP1);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.DUPLICATE_GROUP, e.getReason());
+
+ // do a trivial reanem
+ try {
+ _admin.renameGroup(group, group.getName());
+ } catch (UserMgtException e2) {
+ assertEquals(UserMgtException.Reason.TRIVIAL_RENAME, e2
+ .getReason());
+ return;
+ }
+ fail();
+ return;
+ }
+ fail();
+ }
+
+ /**
+ * Renames a group to a group with an invalid name. Verifies that the
+ * appropriate exception is thrown.
+ *
+ */
+ public void testRenameGroupInvalidGroupname() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ try {
+ _admin.renameGroup(group, "a b");
+ } catch (UserMgtException e) {
+ assertEquals(e.getReason(), Reason.INVALID_GROUPNAME);
+ }
+ }
+
+ /**
+ * Removes a user. Verifies that the user is removed. Verifies that the an
+ * exception is thrown when the user does not exist.
+ *
+ */
+ public void testRemoveUser() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+
+ assertEquals(1, _admin.getUserCount());
+ _admin.removeUser(user);
+ assertEquals(0, _admin.getUserCount());
+
+ _admin.createUser(USER1, PASS1, group);
+ assertEquals(1, _admin.getUserCount());
+
+ User user2 = createUser(USER2, PASS2, group);
+
+ try {
+ _admin.removeUser(user2);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.UNKNOWN_USER, e.getReason());
+ }
+ }
+
+ /**
+ * Removes a group. Verifies that the group is removed. Verifies that the an
+ * exception is thrown when the group does not exist or if there are still
+ * users in the group.
+ *
+ */
+ public void testRemoveGroup() throws UserMgtException {
+ Group group1 = _admin.createGroup(GROUP1);
+ assertEquals(1, _admin.getGroupCount());
+ _admin.removeGroup(group1);
+ assertEquals(0, _admin.getGroupCount());
+ group1 = _admin.createGroup(GROUP1);
+
+ _admin.createUser(USER1, PASS1, group1);
+ try {
+ _admin.removeGroup(group1);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.GROUP_STILL_OCCUPIED, e
+ .getReason());
+ return;
+ }
+ fail();
+ }
+
+ /**
+ * Tries to remove an unknown group. Verifies that an exception is thrown.
+ *
+ */
+ public void testRemoveGroupUnknownGroup() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ Group group2 = new Group(GROUP2);
+ try {
+ _admin.removeGroup(group2);
+ } catch (UserMgtException e) {
+ assertEquals(UserMgtException.Reason.UNKNOWN_GROUP, e.getReason());
+ }
+ }
+
+ /**
+ * Changes the password, verifies that this succeeds.
+ *
+ * @throws UserMgtException
+ */
+ public void testChangePassword() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ User user = _admin.createUser(USER1, PASS1, group);
+ user.changePassword(PASS1, PASS2);
+
+ // retrieve the user and verifies the password hasn't changed.
+ User user2 = _admin.getUser(USER1);
+ try {
+ user2.checkPassword(PASS2);
+ fail(); // password should not have changed already.
+ } catch (UserMgtException e) {
+ // ok.
+ }
+
+ // now notify the admin of the change in the user
+ _admin.userModified(user);
+
+ user2 = _admin.getUser(USER1);
+ user2.checkPassword(PASS2); // this time it should succeed.
+
+ }
+
+ /**
+ * Performance test. Finds a user by name.
+ *
+ */
+ public void testPerformanceFindUserByName() throws UserMgtException {
+ Group group = _admin.createGroup(GROUP1);
+ _admin.createUser(USER1, PASS1, group);
+
+ int n = 1000;
+ long time = System.currentTimeMillis();
+ for (int i = 0; i < n; i++) {
+ _admin.getUser(USER1);
+ }
+ LOGGER.info("Looked up a user " + n + " times in "
+ + (float) (System.currentTimeMillis() - time) / 1000.0);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt;
+
+import org.wamblee.security.encryption.Md5HexMessageDigester;
+import org.wamblee.usermgt.UserMgtException.Reason;
+
+/**
+ * User management test utilities.
+ *
+ * @author Erik Brakkee
+ */
+public class UsermgtTestUtils {
+
+ private static final String DUMMY_GROUP = "dummygroup";
+ private static final String DUMMY_PASSWD = "dummypasswd";
+
+ public static Group createGroup(String aName) {
+ return new Group(aName);
+ }
+
+ public static User createUser(String aUsername) throws UserMgtException {
+ return createUser(aUsername, DUMMY_GROUP);
+ }
+
+ public static User createUser(String aUsername, String aGroup) throws UserMgtException {
+ return createUser(aUsername, createGroup(aGroup));
+ }
+
+ public static User createUser(String aUsername, Group aGroup) throws UserMgtException {
+ return createUser(aUsername, DUMMY_PASSWD, aGroup);
+ }
+
+ public static User createUser(String aName, String aPassword, Group aGroup) throws UserMgtException {
+ return new User(aName, aPassword, aGroup,
+ new RegexpNameValidator(RegexpNameValidator.PASSWORD_PATTERN,
+ Reason.INVALID_PASSWORD, "Password must be at least 6 chars"),
+ new Md5HexMessageDigester());
+ }
+
+ public static void addUserToGroup(User aUser, Group aGroup) throws UserMgtException {
+ aUser.addGroup(aGroup);
+ }
+
+ public static void removeUserFromGroup(User aUser, Group aGroup) throws UserMgtException {
+ aUser.removeGroup(aGroup);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.component.DatabaseTesterComponent;
+import org.wamblee.test.spring.TestTransactionCallback;
+import org.wamblee.usermgt.GroupSet;
+import org.wamblee.usermgt.InMemoryGroupSetTest;
+
+/**
+ * Tests for {@link org.wamblee.usermgt.hibernate.HibernateGroupSet}
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateGroupSetTest extends InMemoryGroupSetTest {
+
+ private static final String GROUP_TABLE = "GROUPS";
+
+ private static final String GROUP_QUERY = "select * from " + GROUP_TABLE + " where name = ?";
+
+ private DefaultContainer _container;
+ private Scope _scope;
+
+ private DatabaseTesterComponent _databaseTester;
+ private GroupSet _groupSet;
+
+ @Override
+ protected void setUp() throws Exception {
+
+ _container = new UserMgtRepositoryTestContainer("top");
+
+ ObjectConfiguration config = new ObjectConfiguration(
+ HibernateGroupSetTest.class);
+ config.getSetterConfig().clear().add(
+ "setGroupSet").add("setDatabaseTester");
+ _container.addComponent("testcase", this, config);
+
+ _scope = _container.start();
+
+ _databaseTester.cleanDatabase();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ _container.stop(_scope);
+ super.tearDown();
+ }
+
+ public void setDatabaseTester(DatabaseTesterComponent aDatabaseTester) {
+ _databaseTester = aDatabaseTester;
+ }
+
+ public void setGroupSet(GroupSet aGroupSet) {
+ _groupSet = aGroupSet;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupCount(int)
+ */
+ @Override
+ protected void checkGroupCount(int aSize) throws SQLException {
+ _databaseTester.flush();
+ super.checkGroupCount(aSize);
+ assertEquals(aSize, _databaseTester.getTableSize(GROUP_TABLE));
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupExists(java.lang.String)
+ */
+ @Override
+ protected void checkGroupExists(final String aGroup) throws SQLException {
+ _databaseTester.flush();
+ Map<String,Integer> result =
+ _databaseTester.executeTransaction(new TestTransactionCallback() {
+ /* (non-Javadoc)
+ * @see org.wamblee.test.TestTransactionCallback#execute()
+ */
+ @Override
+ public Map execute() throws Exception {
+ ResultSet result = _databaseTester.executeQuery(GROUP_QUERY, aGroup);
+ Map<String,Integer> res = new HashMap<String,Integer>();
+ res.put("result", _databaseTester.countResultSet(result));
+ return res;
+ }
+ });
+
+ int count = result.get("result");
+ assertEquals(1, count);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupNotExists(java.lang.String)
+ */
+ @Override
+ protected void checkGroupNotExists(String aGroup) throws SQLException {
+ _databaseTester.flush();
+ ResultSet result = _databaseTester.executeQuery(GROUP_QUERY, aGroup);
+ assertEquals(0, _databaseTester.countResultSet(result));
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#createGroupSet()
+ */
+ @Override
+ protected GroupSet createGroupSet() {
+ return _groupSet;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.cache.EhCache;
+import org.wamblee.system.adapters.ClassConfiguration;
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.components.DatabaseComponentFactory;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.component.DatabaseTesterComponent;
+import org.wamblee.system.spring.component.DatasourceComponent;
+import org.wamblee.test.spring.TestTransactionCallbackWithoutResult;
+import org.wamblee.usermgt.UserAdministration;
+import org.wamblee.usermgt.UserAdministrationImplTest;
+
+/**
+ * User administration tests with persistence based on Hibernate. This executes
+ * the same test cases as {@link org.wamblee.usermgt.UserAdministrationImplTest}
+ * with in addition, one test case that executes all Hibernate test cases
+ * separately with each test case in its own transaction.
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateUserAdministrationTest extends UserAdministrationImplTest {
+
+ private static final Log LOG = LogFactory.getLog(HibernateUserAdministrationTest.class);
+
+ private DefaultContainer _container;
+ private Scope _scope;
+
+ private DatabaseTesterComponent _databaseTester;
+ private EhCache<Serializable, Serializable> _userCache;
+ private UserAdministration _userAdmin;
+
+
+ /* (non-Javadoc)
+ * @see org.wamblee.usermgt.UserAdministrationImplTest#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+
+ _container = new DefaultContainer("top");
+ DatabaseComponentFactory.addDatabaseConfig(_container);
+ _container.addComponent(new DatasourceComponent("datasource"));
+ _container.addComponent(new UserAdministrationComponent("admin", true));
+
+ ClassConfiguration dbtesterConfig = new ClassConfiguration(DatabaseTesterComponent.class);
+ dbtesterConfig.getObjectConfig().getSetterConfig().initAllSetters();
+ _container.addComponent("databaseTester", dbtesterConfig);
+
+ ObjectConfiguration config = new ObjectConfiguration(
+ HibernateUserAdministrationTest.class);
+ config.getSetterConfig().clear().add(
+ "setUserCache").add("setDatabaseTester").add("setUserAdmin");
+ _container.addComponent("testcase", this, config);
+
+ _scope = _container.start();
+
+ _databaseTester.cleanDatabase();
+
+ super.setUp();
+ clearUserCache();
+ }
+
+ public void setUserCache(EhCache<Serializable, Serializable> aUserCache) {
+ _userCache = aUserCache;
+ }
+
+ public void setDatabaseTester(DatabaseTesterComponent aDatabaseTester) {
+ _databaseTester = aDatabaseTester;
+ }
+
+ public void setUserAdmin(UserAdministration aUserAdmin) {
+ _userAdmin = aUserAdmin;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ _container.stop(_scope);
+ super.tearDown();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.UserAdministrationImplTest#createAdmin()
+ */
+ @Override
+ protected UserAdministration createAdmin() {
+ return _userAdmin;
+ }
+
+ public void testAllTestsInASeparateTransaction() throws SQLException {
+
+ Method[] methods = UserAdministrationImplTest.class.getMethods();
+ for (final Method method : methods) {
+ if (method.getName().startsWith("test")) {
+ _databaseTester.cleanDatabase();
+ clearUserCache();
+ _databaseTester.executeTransaction(new TestTransactionCallbackWithoutResult() {
+ public void execute() throws Exception {
+ LOG.info("Running test " + method.getName());
+ try {
+ method.invoke(HibernateUserAdministrationTest.this);
+ } catch (Throwable t) {
+ LOG.error("Test " + method.getName() + " failed");
+ throw new RuntimeException(t.getMessage(), t);
+ }
+ finally {
+ LOG.info("Test " + method.getName() + " finished");
+ }
+
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private void clearUserCache() {
+ _userCache.clear();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Set;
+
+import org.wamblee.cache.EhCache;
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.component.DatabaseTesterComponent;
+import org.wamblee.usermgt.Group;
+import org.wamblee.usermgt.GroupSet;
+import org.wamblee.usermgt.InMemoryUserSetTest;
+import org.wamblee.usermgt.User;
+import org.wamblee.usermgt.UserMgtException;
+import org.wamblee.usermgt.UserSet;
+
+/**
+ * Tests for {@link org.wamblee.usermgt.hibernate.HibernateGroupSet}
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateUserSetTest extends InMemoryUserSetTest {
+
+ private static final String USER_TABLE = "USERS";
+ private static final String GROUP_TABLE = "GROUPS";
+
+ private static final String USER_QUERY = "select * from " + USER_TABLE
+ + " where name = ?";
+ private static final String GROUP_QUERY = "select * from " + GROUP_TABLE
+ + " where name = ?";
+
+ private DefaultContainer _container;
+ private Scope _scope;
+
+ private UserSet _userset;
+ private GroupSet _groupset;
+ private EhCache<Serializable, Serializable> _userCache;
+ private DatabaseTesterComponent _databaseTester;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryUserSetTest#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+
+ _container = new UserMgtRepositoryTestContainer("top");
+
+ ObjectConfiguration config = new ObjectConfiguration(
+ HibernateUserSetTest.class);
+ config.getSetterConfig().clear().add("setUserset").add(
+ "setGroupset").add("setDatabaseTester").add("setUserCache");
+ _container.addComponent("testcase", this, config);
+
+ _scope = _container.start();
+
+ clearUserCache();
+ _databaseTester.cleanDatabase();
+
+ super.setUp();
+ }
+
+ public void setUserset(UserSet aUserset) {
+ _userset = aUserset;
+ }
+
+ public void setGroupset(GroupSet aGroupset) {
+ _groupset = aGroupset;
+ }
+
+ public void setUserCache(EhCache<Serializable, Serializable> aUserCache) {
+ _userCache = aUserCache;
+ }
+
+ public void setDatabaseTester(DatabaseTesterComponent aDatabaseTester) {
+ _databaseTester = aDatabaseTester;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ _container.stop(_scope);
+ super.tearDown();
+ }
+
+ /**
+ * Clears the user cache.
+ */
+ private void clearUserCache() {
+ _userCache.clear();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupCount(int)
+ */
+ @Override
+ protected void checkUserCount(int aSize) throws SQLException {
+ _databaseTester.flush();
+ super.checkUserCount(aSize);
+ assertEquals(aSize, _databaseTester.getTableSize(USER_TABLE));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupExists(java.lang.String)
+ */
+ @Override
+ protected void checkUserExists(String aUser) throws SQLException {
+ _databaseTester.flush();
+ ResultSet result = _databaseTester.executeQuery(USER_QUERY, aUser);
+ assertEquals(1, _databaseTester.countResultSet(result));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupNotExists(java.lang.String)
+ */
+ @Override
+ protected void checkUserNotExists(String aUser) throws SQLException {
+ _databaseTester.flush();
+ ResultSet result = _databaseTester.executeQuery(USER_QUERY, aUser);
+ assertEquals(0, _databaseTester.countResultSet(result));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupCount(int)
+ */
+ @Override
+ protected void checkGroupCount(int aSize) throws SQLException {
+ _databaseTester.flush();
+ assertEquals(aSize, _databaseTester.getTableSize(GROUP_TABLE));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupExists(java.lang.String)
+ */
+ @Override
+ protected void checkGroupExists(String aGroup) throws SQLException {
+ _databaseTester.flush();
+
+ ResultSet result = _databaseTester.executeQuery(GROUP_QUERY, aGroup);
+ assertEquals(1, _databaseTester.countResultSet(result));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#checkGroupNotExists(java.lang.String)
+ */
+ @Override
+ protected void checkGroupNotExists(String aGroup) throws SQLException {
+ _databaseTester.flush();
+ ResultSet result = _databaseTester.executeQuery(GROUP_QUERY, aGroup);
+ assertEquals(0, _databaseTester.countResultSet(result));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryGroupSetTest#createGroupSet()
+ */
+ @Override
+ protected UserSet createUserSet() {
+ return _userset;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.usermgt.InMemoryUserSetTest#createGroupSet()
+ */
+ @Override
+ protected GroupSet createGroupSet() {
+ return _groupset;
+ }
+
+ /**
+ * Reproduction of a bug. Create a user which is in group1 Add it to a
+ * second group group2. Remove the user from group1. Verify the user is in
+ * group2.
+ */
+ public void testVerifyAddRemove() throws SQLException, UserMgtException {
+ _databaseTester.cleanDatabase(); // just to be sure.
+ GroupSet groups = getGroups();
+ assertEquals(0, groups.size());
+ Group group1 = createGroup("group1");
+ Group group2 = createGroup("group2");
+ groups.add(group1);
+ groups.add(group2);
+ checkGroupExists("group1");
+ checkGroupExists("group2");
+
+ User user = createUser("user", PASSWORD, group1);
+ getUsers().add(user);
+ checkUserExists("user");
+
+ addUserToGroup(user, group2);
+ getUsers().userModified(user);
+ clearUserCache();
+ User user2 = getUsers().find("user");
+ Set<Group> userGroups = user2.getGroups();
+ assertTrue(user2.isInGroup("group1"));
+ assertTrue(user2.isInGroup("group2"));
+ assertEquals(2, userGroups.size());
+
+ removeUserFromGroup(user, group1);
+ getUsers().userModified(user);
+ clearUserCache();
+ user2 = getUsers().find("user");
+ userGroups = user2.getGroups();
+ assertFalse(user2.isInGroup("group1"));
+ assertTrue(user2.isInGroup("group2"));
+ assertEquals(1, userGroups.size());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.usermgt.hibernate;
+
+import java.io.IOException;
+
+import org.wamblee.system.adapters.ClassConfiguration;
+import org.wamblee.system.adapters.DefaultContainer;
+import org.wamblee.system.adapters.ObjectConfiguration;
+import org.wamblee.system.components.DatabaseComponentFactory;
+import org.wamblee.system.spring.component.DatabaseTesterComponent;
+import org.wamblee.system.spring.component.DatasourceComponent;
+import org.wamblee.system.spring.component.HibernateComponent;
+import org.wamblee.usermgt.UserGroupRepositoryComponent;
+
+/**
+ *
+ * Test container for repository tests of user management.
+ *
+ * @author Erik Brakkee
+ */
+public class UserMgtRepositoryTestContainer extends DefaultContainer {
+
+ public UserMgtRepositoryTestContainer(String aName) throws IOException {
+ super(aName);
+ DatabaseComponentFactory.addDatabaseConfig(this);
+ addComponent(new DatasourceComponent("datasource"));
+
+ ObjectConfiguration mappingFilesConfig = new ObjectConfiguration(UsermgtHibernateMappingFiles.class);
+ mappingFilesConfig.getSetterConfig().initAllSetters();
+ addComponent("mappingFiles", new UsermgtHibernateMappingFiles(), mappingFilesConfig);
+ addComponent(new HibernateComponent("hibernate"));
+ addComponent(new UserGroupRepositoryComponent("usersgroups"));
+ ClassConfiguration dbtesterConfig = new ClassConfiguration(DatabaseTesterComponent.class);
+ dbtesterConfig.getObjectConfig().getSetterConfig().initAllSetters();
+ addComponent("databaseTester", dbtesterConfig);
+
+ }
+
+}
--- /dev/null
+
+
+# Database properties for test runs in a J2SE environment.
+
+# See ORMappingConfig.DatabaseType for allowed values
+database.type=MYSQL_INNO_DB
+
+database.driver=com.mysql.jdbc.Driver
+database.url=jdbc:mysql://localhost:3306/test
+database.username=erik
+database.password=abc123
\ No newline at end of file
--- /dev/null
+
+##############################################################################
+# Name of the administrators group
+##############################################################################
+org.wamblee.security.admingroup=administrators
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <!-- This is the Spring configuration to define the database-related stuff for the
+ all persistence tests. -->
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+ <!-- driver classname no longer needed because ServiceLoader is used these days
+ <property name="driverClassName">
+ <value>${database.driver}</value>
+ </property>
+ -->
+ <property name="url">
+ <value>${database.url}</value>
+ </property>
+ <property name="username">
+ <value>${database.username}</value>
+ </property>
+ <property name="password">
+ <value>${database.password}</value>
+ </property>
+ </bean>
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="propertyBean"
+ class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+ <property name="locations">
+ <list>
+ <value>properties/test.org.wamblee.security.hibernate.properties</value>
+ <value>properties/test.org.wamblee.security.usermgt.properties</value>
+ <value>properties/test.org.wamblee.security.database.properties</value>
+ </list>
+ </property>
+ </bean>
+ </beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <bean id="org.wamblee.usermgt.UserAccessor"
+ class="org.wamblee.security.authorization.TestUserAccessor">
+
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="usermgtInitializer"
+ class="org.wamblee.usermgt.UserAdminInitializer">
+ <constructor-arg><ref local="org.wamblee.usermgt.UserAdministration"/></constructor-arg>
+ <!-- users -->
+ <constructor-arg>
+ <list>
+ <value>erik</value>
+ <value>admin</value>
+ </list>
+ </constructor-arg>
+ <!-- groups -->
+ <constructor-arg>
+ <list>
+ <value>users</value>
+ <value>${org.wamblee.security.admingroup}</value>
+ </list>
+ </constructor-arg>
+ <!-- passwords -->
+ <constructor-arg>
+ <list>
+ <value>abc123</value>
+ <value>abc123</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <packaging>jar</packaging>
+ <name>/support/general</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>oro</groupId>
+ <artifactId>oro</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbyclient</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbynet</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </dependency>
+ <!-- should be possible to remove the dependence on log4j -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * The <code>Cache</code> interface represents... a cache.
+ * In some circumstances it is more optimal to implement caching directly in
+ * the code instead of relying on Hibernate caching methods. This interface abstracts
+ * from the used cache implementation.
+ * Cache implementations must be thread-safe.
+ */
+public interface Cache<KeyType extends Serializable, ValueType extends Serializable> {
+
+ /**
+ * Adds a key-value pair to the cache.
+ * @param aKey Key.
+ * @param aValue Value.
+ */
+ void put(KeyType aKey, ValueType aValue);
+
+ /**
+ * Retrieves a value from the cache.
+ * @param aKey Key to retrieve.
+ * @return Key.
+ */
+ ValueType get(KeyType aKey);
+
+ /**
+ * Removes an entry from the cache.
+ * @param aKey Key to remove the entry for.
+ */
+ void remove(KeyType aKey);
+
+ /**
+ * Removes all entries from the cache.
+ *
+ */
+ void clear();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Represents a cached object. The object is either retrieved from the cache if
+ * the cache has it, or a call back is invoked to get the object (and put it in
+ * the cache).
+ *
+ * @author Erik Brakkee
+ */
+public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
+
+ private static final Logger LOGGER = Logger.getLogger(CachedObject.class);
+
+ /**
+ * Callback invoked to compute an object if it was not found in the cache.
+ *
+ * @param <T>
+ * Type of the object
+ */
+ public static interface Computation<Key extends Serializable, Value extends Serializable> {
+ /**
+ * Gets the object. Called when the object is not in the cache.
+ *
+ * @param aObjectKey
+ * Id of the object in the cache.
+ * @return Object, must be non-null.
+ */
+ Value getObject(Key aObjectKey);
+ }
+
+ /**
+ * Cache to use.
+ */
+ private Cache<KeyType, ValueType> _cache;
+
+ /**
+ * Key of the object in the cache.
+ */
+ private KeyType _objectKey;
+
+ /**
+ * Computation used to obtain the object if it is not found in the cache.
+ */
+ private Computation<KeyType, ValueType> _computation;
+
+ /**
+ * Constructs the cached object.
+ *
+ * @param aCache
+ * Cache to use.
+ * @param aObjectKey
+ * Key of the object in the cache.
+ * @param aComputation
+ * Computation to get the object in case the object is not in the
+ * cache.
+ */
+ public CachedObject(Cache<KeyType, ValueType> aCache, KeyType aObjectKey,
+ Computation<KeyType, ValueType> aComputation) {
+ _cache = aCache;
+ _objectKey = aObjectKey;
+ _computation = aComputation;
+ }
+
+ /**
+ * Gets the object. Since the object is cached, different calls to this
+ * method may return different objects.
+ *
+ * @return Object.
+ */
+ public ValueType get() {
+ ValueType object = (ValueType) _cache.get(_objectKey); // the used
+ // cache is
+ // thread safe.
+ if (object == null) {
+ // synchronize the computation to make sure that the object is only
+ // computed
+ // once when multiple concurrent threads detect that the entry must
+ // be
+ // recomputed.
+ synchronized (this) {
+ object = (ValueType) _cache.get(_objectKey);
+ if (object == null) {
+ // No other thread did a recomputation so we must do this
+ // now.
+ LOGGER.debug("Refreshing cache for '" + _objectKey + "'");
+ object = _computation.getObject(_objectKey);
+ _cache.put(_objectKey, object);
+ }
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Invalidates the cache for the object so that it is recomputed the next
+ * time it is requested.
+ *
+ */
+ public void invalidate() {
+ _cache.remove(_objectKey);
+ }
+
+ /**
+ * Gets the cache.
+ *
+ * @return Cache.
+ */
+ public Cache getCache() {
+ return _cache;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.log4j.Logger;
+import org.wamblee.io.InputResource;
+
+/**
+ * Cache implemented on top of EhCache.
+ *
+ * @author Erik Brakkee
+ */
+public class EhCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements org.wamblee.cache.Cache<KeyType, ValueType> {
+
+ private static final Logger LOGGER = Logger.getLogger(EhCache.class);
+
+ /**
+ * EH Cache manager.
+ */
+ private CacheManager _manager;
+
+ /**
+ * EH cache.
+ */
+ private Cache _cache;
+
+ /**
+ * Constructs a cache based on EHCache.
+ *
+ * @param aResource
+ * Resource containing the configuration file for EHCache.
+ * @param aCacheName
+ * Name of the cache to use. If a cache with this name does not
+ * exist, one is created based on default settings.
+ * @throws IOException
+ * @throws CacheException
+ */
+ public EhCache(InputResource aResource, String aCacheName)
+ throws IOException, CacheException {
+ InputStream is = aResource.getInputStream();
+ try {
+ _manager = new CacheManager(is);
+ _cache = _manager.getCache(aCacheName);
+ if (_cache == null) {
+ LOGGER.warn("Creating cache '" + aCacheName
+ + "' because it is not configured");
+ _manager.addCache(aCacheName);
+ _cache = _manager.getCache(aCacheName);
+ }
+ assert _cache != null;
+ } finally {
+ is.close();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ _cache.put(new Element(aKey, aValue));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ try {
+ Element element = _cache.get(aKey);
+ if (element == null) {
+ return null;
+ }
+ return (ValueType) element.getValue();
+ } catch (CacheException e) {
+ throw new RuntimeException("Cache problem key = '" + aKey + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ _cache.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ _cache.removeAll();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+/**
+ * A very simple cache based on a HashMap, It never expires any entries, and has
+ * no bounds on its size.
+ *
+ * @author Erik Brakkee
+ */
+public class ForeverCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ /**
+ * Cached entries.
+ */
+ private HashMap<KeyType, ValueType> _map;
+
+ /**
+ * Constructs the cache.
+ *
+ */
+ public ForeverCache() {
+ _map = new HashMap<KeyType, ValueType>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public synchronized void put(KeyType aKey, ValueType aValue) {
+ _map.put(aKey, aValue);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public synchronized ValueType get(KeyType aKey) {
+ return _map.get(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public synchronized void remove(KeyType aKey) {
+ _map.remove(aKey);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public synchronized void clear() {
+ _map.clear();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.Serializable;
+
+/**
+ * A cache that does not cache. This implementation is useful for disabling caching.
+ * Because of this implementation, application code does not need to distinguish
+ * between the situation where it a cache is used and where it isn't.
+ *
+ * @author Erik Brakkee
+ */
+public class ZeroCache<KeyType extends Serializable, ValueType extends Serializable>
+ implements Cache<KeyType, ValueType> {
+
+ public ZeroCache() {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#put(KeyType, ValueType)
+ */
+ public void put(KeyType aKey, ValueType aValue) {
+ // Empty.
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#get(KeyType)
+ */
+ public ValueType get(KeyType aKey) {
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#remove(KeyType)
+ */
+ public void remove(KeyType aKey) {
+ // Empty
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.cache.Cache#clear()
+ */
+ public void clear() {
+ // Empty
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides an interface for a cache together with several
+implementations.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.collections;
+
+import java.util.Collection;
+
+import org.wamblee.conditions.Condition;
+
+public class CollectionFilter {
+
+ /**
+ * Filters a collection by adding all elements in the from collection
+ * that satisfy a given condition to the to collection.
+ * @param <T> Type of contained element.
+ * @param aFrom From container to which the condition is applied.
+ * @param aTo To container to which matching elements are added.
+ * @param aCondition Condition by which elements are matched.
+ */
+ public static <T> void filter(Collection<T> aFrom, Collection<T> aTo, Condition<T> aCondition) {
+ for (T t: aFrom) {
+ if ( aCondition.matches(t)) {
+ aTo.add(t);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * In memory JVM lock.
+ *
+ * @author Erik Brakkee
+ */
+public class JvmLock implements Lock {
+
+ /**
+ * Reentrant lock to use.
+ */
+ private ReentrantLock _lock;
+
+ /**
+ * In-memory lock.
+ */
+ public JvmLock() {
+ _lock = new ReentrantLock(true);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#acquire()
+ */
+ public void acquire() {
+ _lock.lock();
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.concurrency.Lock#release()
+ */
+ public void release() {
+ _lock.unlock();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+/**
+ * Represents a re-entrant lock.
+ * Implementations can provide inmemory JVM locking or full cluster safe locking
+ * mechanisms.
+ *
+ * @author Erik Brakkee
+ */
+public interface Lock {
+
+ /**
+ * Acquires the lock.
+ */
+ void acquire();
+
+ /**
+ * Releases the lock.
+ */
+ void release();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import java.util.HashSet;
+
+
+/**
+ * Read-write lock for allowing multiple concurrent readers or at most one
+ * writer. This implementation does not aim for high performance but for
+ * robustness and simplicity. Users of this class should not synchronize on
+ * objects of this class.
+ */
+public class ReadWriteLock {
+ /**
+ * Sets containing the references to the threads that are currently
+ * reading. This administration is useful to check that the lock has
+ * already been acquired before it is release. This check adds robustness
+ * to the application.
+ */
+ private HashSet<Thread> _readers;
+
+ /**
+ * The thread that has acquired the lock for writing or null if no such
+ * thread exists currently.
+ */
+ private Thread _writer;
+
+ /**
+ * Constructs read-write lock.
+ */
+ public ReadWriteLock() {
+ _readers = new HashSet<Thread>();
+ _writer = null;
+ }
+
+ /**
+ * Acquires the lock for reading. This call will block until the lock can
+ * be acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireRead() {
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Read lock already acquired by current thread: "
+ + Thread.currentThread());
+ }
+
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire the read lock while already holding a write lock: "
+ + Thread.currentThread());
+ }
+
+ while (_writer != null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _readers.add(Thread.currentThread());
+ }
+
+ /**
+ * Releases the lock for reading. Note: This implementation assumes that
+ * the lock has already been acquired for reading previously.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired by
+ * this thread.
+ */
+ public synchronized void releaseRead() {
+ if (!_readers.remove(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Cannot release read lock because current thread has not acquired it.");
+ }
+
+ if (_readers.size() == 0) {
+ notifyAll();
+ }
+ }
+
+ /**
+ * Acquires the lock for writing. This call will block until the lock has
+ * been acquired.
+ *
+ * @throws IllegalStateException Thrown if the read or write lock is
+ * already acquired.
+ */
+ public synchronized void acquireWrite() {
+ if (_writer == Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the write lock: "
+ + Thread.currentThread());
+ }
+
+ if (_readers.contains(Thread.currentThread())) {
+ throw new IllegalStateException(
+ "Trying to acquire a write lock while already holding the read lock: "
+ + Thread.currentThread());
+ }
+
+ // wait until there are no more writers and no more
+ // readers
+ while ((_writer != null) || (_readers.size() > 0)) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ notifyAll();
+ }
+ }
+
+ _writer = Thread.currentThread();
+
+ // notification not necessary since all writers and
+ // readers are now blocked by this thread.
+ }
+
+ /**
+ * Releases the lock for writing.
+ *
+ * @throws IllegalStateException Thrown when the lock was not acquired.
+ */
+ public synchronized void releaseWrite() {
+ if (_writer != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Cannot release write lock because it was not acquired. ");
+ }
+
+ _writer = null;
+ notifyAll();
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides utilities for dealing with concurrency.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical and of different boolean conditions.
+ *
+ * @author Erik Brakkee
+ */
+public class AndCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public AndCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the and condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical and.
+ */
+ public AndCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (!condition.matches(aObject)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+
+/**
+ * Determines if an object matches a certain condition.
+ *
+ * @author Erik Brakkee
+ */
+public interface Condition<T> {
+
+ /**
+ * Determines if an object matches a condition.
+ * @param aObject object to match.
+ * @return True iff the object matches.
+ */
+ boolean matches(T aObject);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Condition which always returns a fixed value.
+ *
+ * @author Erik Brakkee
+ */
+public class FixedCondition<T> implements Condition<T> {
+
+ private boolean _value;
+
+ /**
+ * Constructs the condition.
+ * @param aValue Fixed value of the condition.
+ */
+ public FixedCondition(boolean aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ return _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a logical or of different boolean conditions.
+ *
+ * @author Erik Brakkee
+ */
+public class OrCondition<T> implements Condition<T> {
+
+ private List<Condition<T>> _conditions;
+
+ /**
+ * Constructs the condition.
+ *
+ * @param aCondition1
+ * First condition.
+ * @param aCondition2
+ * Second condition.
+ */
+ public OrCondition(Condition<T> aCondition1, Condition<T> aCondition2) {
+ _conditions = new ArrayList<Condition<T>>();
+ _conditions.add(aCondition1);
+ _conditions.add(aCondition2);
+ }
+
+ /**
+ * Constructs the or condition.
+ *
+ * @param aConditions
+ * List of conditions to use in the logical or.
+ */
+ public OrCondition(List<Condition<T>> aConditions) {
+ _conditions = aConditions;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.crawler.kiss.ProgramMatcher#matches(org.wamblee.crawler.kiss.Program)
+ */
+ public boolean matches(T aObject) {
+ for (Condition<T> condition : _conditions) {
+ if (condition.matches(aObject)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+/**
+ * Condition to check whether a given property value matches a certain
+ * regular expression.
+ *
+ * @author Erik Brakkee
+ */
+public class PropertyRegexCondition<T> implements Condition<T> {
+
+ /**
+ * Property name.
+ */
+ private String _property;
+
+ /**
+ * Regular expression.
+ */
+ private Pattern _regex;
+
+ /**
+ * Whether or not to convert the value to lowercase before matching.
+ */
+ private boolean _tolower;
+
+ /**
+ * Constructs the condition.
+ * @param aProperty Name of the property to examine.
+ * @param aRegex Regular expression to use.
+ * @param aTolower Whether or not to convert the value to lowercase before matching.
+ */
+ public PropertyRegexCondition(String aProperty, String aRegex, boolean aTolower) {
+ _property = aProperty;
+ _regex = Pattern.compile(aRegex);
+ _tolower = aTolower;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(T aObject) {
+ try {
+ String value = PropertyUtils.getProperty(aObject, _property) + "";
+ if ( _tolower ) {
+ value = value.toLowerCase();
+ }
+ Matcher matcher = _regex.matcher(value);
+ return matcher.matches();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides some basic support classes for checking boolean conditions
+on objects.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Bean factory used to obtain objects in a transparent way.
+ *
+ * @author Erik Brakkee
+ */
+public interface BeanFactory {
+
+ /**
+ * Finds a bean based on id.
+ * @param aId Id of the bean.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ Object find(String aId);
+
+ /**
+ * Finds a bean of the given class and which can be cast to the
+ * specified class. This is typically used by specifying the interface
+ * class for retrieving an implementation of that class. This
+ * means that the bean implementing the class is configured in the bean factory
+ * with id equal to the class name of the interface.
+ * @param aClass Class of the object to find.
+ * @return Object (always non-null).
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(Class<T> aClass);
+
+ /**
+ * Finds a bean with the given id which can be cast to the specified
+ * class.
+ * @param <T> Type of the object to get.
+ * @param aId Id of the object to lookup.
+ * @param aClass Class that the object must extends.
+ * @return Object, always non-null.
+ * @throws BeanFactoryException In case the object could not be found.
+ */
+ <T> T find(String aId, Class<T> aClass);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Exception thrown by the BeanFactory if an object could not be found.
+ *
+ * @author Erik Brakkee
+ */
+public class BeanFactoryException extends RuntimeException {
+ static final long serialVersionUID = -1215992188624874902L;
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public BeanFactoryException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ * @param aThrowable Cause of the exception.
+ */
+ public BeanFactoryException(String aMsg, Throwable aThrowable) {
+ super(aMsg, aThrowable);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * The standard means to obtain the bean factory. This works by reading a
+ * property {@value #BEAN_FACTORY_CLASS} from a property file named
+ * {@value #BEAN_KERNEL_PROP_FILE} from the class path. This property identifies
+ * the bean factory implementation to use. The configured bean factory must have
+ * a no-arg constructor.
+ */
+public final class BeanKernel {
+
+ private static final Log LOG = LogFactory.getLog(BeanKernel.class);
+
+ /**
+ * Bean factory kernel properties file.
+ */
+ private static final String BEAN_KERNEL_PROP_FILE = "org.wamblee.beanfactory.properties";
+
+ /**
+ * Name of the property to define the name of the bean factory class to use.
+ * THis class must have a public default constructor.
+ */
+ private static final String BEAN_FACTORY_CLASS = "org.wamblee.beanfactory.class";
+
+ /**
+ * Cached bean factory.
+ */
+ private static BeanFactory BEAN_FACTORY;
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private BeanKernel() {
+ // Empty
+ }
+
+ /**
+ * Overrides the default mechanism for looking up the bean factory by
+ * specifying it yourself.
+ *
+ * @param aOverride
+ * Override bean factory.
+ */
+ public static void overrideBeanFactory(BeanFactory aOverride) {
+ BEAN_FACTORY = aOverride;
+ }
+
+ /**
+ * Gets the bean factory.
+ *
+ * @return Bean factory.
+ */
+ public static BeanFactory getBeanFactory() {
+ synchronized (BeanFactory.class) {
+ if (BEAN_FACTORY == null) {
+ BEAN_FACTORY = lookupBeanFactory(BEAN_KERNEL_PROP_FILE);
+ }
+ }
+ return BEAN_FACTORY;
+ }
+
+ /**
+ * Lookup the bean factory based on the properties file.
+ *
+ * @return Bean factory.
+ */
+ static BeanFactory lookupBeanFactory(String aPropertyFilename) {
+ InputResource resource = new ClassPathResource(aPropertyFilename);
+ InputStream is;
+ try {
+ is = resource.getInputStream();
+ } catch (IOException e) {
+ throw new BeanFactoryException("Cannot open resource " + resource,
+ e);
+ }
+ try {
+ Properties props = new Properties();
+ props.load(is);
+ String className = props.getProperty(BEAN_FACTORY_CLASS);
+ Class beanFactory = Class.forName(className);
+ return (BeanFactory) beanFactory.newInstance();
+ } catch (Exception e) {
+ throw new BeanFactoryException("Cannot read from resource "
+ + resource, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // last resort cannot do much now.
+ LOG.error("Error closing resource " + resource);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import java.io.IOException;
+import java.io.File;
+import java.net.URLClassLoader;
+import java.net.URL;
+import java.lang.reflect.Method;
+
+/**
+ * Utility for working with the class loader. Based on the ClassPathHacker
+ * example found on the internet.
+ */
+public class ClassLoaderUtils {
+
+ // No logging in this class to keep the required class libraries
+ // limited to the standard java classes. This allows use of the
+ // utilities in an environment with a very limited classpath.
+
+ private static final String JAR_SUFFIX = ".jar";
+
+ /**
+ * Adds all jars in the given directory to the class path.
+ * @param aDirectory Directory.
+ * @throws IOException
+ */
+ public static void addJarsInDirectory(File aDirectory) throws IOException {
+ System.out.println("directory '" + aDirectory + "'");
+
+ for (File aFile : aDirectory.listFiles()) {
+ System.out
+ .println("Considering '" + aFile.getCanonicalPath() + "'");
+ if (aFile.getName().toLowerCase().endsWith(JAR_SUFFIX)) {
+ System.out.println("Adding '" + aFile.getCanonicalPath()
+ + "' to classpath.");
+ addFile(aFile);
+ }
+ }
+ }
+
+ /**
+ * Adds a file to the classpath.
+ * @param aFilename Filename to add.
+ * @throws IOException
+ */
+ public static void addFile(String aFilename) throws IOException {
+ File f = new File(aFilename);
+ addFile(f);
+ }
+
+ /**
+ * Adds a file to the classpath.
+ * @param aFile File to add.
+ * @throws IOException
+ */
+ public static void addFile(File aFile) throws IOException {
+ addURL(aFile.toURL());
+ }
+
+ /**
+ * Adds a url to the classpath.
+ * @param aUrl Url to add.
+ * @throws IOException
+ */
+ public static void addURL(URL aUrl) throws IOException {
+
+ URLClassLoader sysloader = (URLClassLoader) ClassLoader
+ .getSystemClassLoader();
+ Class sysclass = URLClassLoader.class;
+
+ try {
+ Method method = sysclass.getDeclaredMethod("addURL", new Class[]{ URL.class } );
+ method.setAccessible(true);
+ method.invoke(sysloader, new Object[] { aUrl });
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw new IOException(
+ "Error, could not add URL to system classloader");
+ }
+
+ }
+
+}
--- /dev/null
+
+
+
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+/**
+ * Represents a pair of objects. This is inspired on the C++ Standard Template
+ * Library pair template.
+ *
+ * @param <T>
+ * Type of the first object.
+ * @param <U>
+ * Type of the second object.
+ *
+ * @author Erik Brakkee
+ */
+public class Pair<T, U> {
+
+ private T _t;
+
+ private U _u;
+
+ /**
+ * Constructs the pair.
+ *
+ * @param aT
+ * First object.
+ * @param aU
+ * Second object.
+ */
+ public Pair(T aT, U aU) {
+ _t = aT;
+ _u = aU;
+ }
+
+ /**
+ * Copies a pair.
+ *
+ * @param aPair
+ * Pair to copy.
+ */
+ public Pair(Pair<T, U> aPair) {
+ _t = aPair._t;
+ _u = aPair._u;
+ }
+
+ /**
+ * Gets the first object of the pair.
+ *
+ * @return First object.
+ */
+ public T getFirst() {
+ return _t;
+ }
+
+ /**
+ * Gets the second object of the pair.
+ *
+ * @return Second object.
+ */
+ public U getSecond() {
+ return _u;
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several general purpose support classes.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents an input resource in the classpath.
+ */
+public class ClassPathResource implements InputResource {
+ /**
+ * Resource name.
+ */
+ private String _resource;
+
+ /**
+ * Construct the class path resource.
+ *
+ * @param aResource
+ * Resource
+ */
+ public ClassPathResource(String aResource) {
+ _resource = aResource;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream stream = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream(_resource);
+ if (stream == null) {
+ throw new IOException("Class path resource '" + _resource
+ + "' not found.");
+ }
+ return stream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "ClassPathResource(" + _resource + ")";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Monitors a directory for changes.
+ *
+ * @author Erik Brakkee
+ */
+public class DirectoryMonitor {
+
+ private static final Log LOG = LogFactory.getLog(DirectoryMonitor.class);
+
+ public static interface Listener {
+
+ void fileChanged(File aFile);
+
+ void fileCreated(File aFile);
+
+ void fileDeleted(File aFile);
+ };
+
+ private File _directory;
+ private FileFilter _filter;
+ private Listener _listener;
+ private Map<File, Date> _contents;
+
+ public DirectoryMonitor(File aDirectory, FileFilter aFilefilter,
+ Listener aListener) {
+ _directory = aDirectory;
+ if (!_directory.isDirectory()) {
+ throw new IllegalArgumentException("Directory '" + _directory
+ + "' does not exist");
+ }
+ _filter = aFilefilter;
+ _listener = aListener;
+ _contents = new HashMap<File, Date>();
+ }
+
+ /**
+ * Polls the directory for changes and notifies the listener of any changes.
+ * In case of any exceptions thrown by the listener while handling the changes,
+ * the next call to this method will invoked the listeners again for the same changes.
+ */
+ public void poll() {
+ LOG.debug("Polling " + _directory);
+ Map<File, Date> newContents = new HashMap<File, Date>();
+ File[] files = _directory.listFiles(_filter);
+
+ // Check deleted files.
+ Set<File> deletedFiles = new HashSet<File>(_contents.keySet());
+ for (File file : files) {
+ if (file.isFile()) {
+ if (_contents.containsKey(file)) {
+ deletedFiles.remove(file);
+ }
+ }
+ }
+ for (File file : deletedFiles) {
+ _listener.fileDeleted(file);
+ }
+
+ for (File file : files) {
+ if (file.isFile()) {
+ if (_contents.containsKey(file)) {
+ Date oldDate = _contents.get(file);
+ if (file.lastModified() != oldDate.getTime()) {
+ _listener.fileChanged(file);
+ } else {
+ // No change.
+ }
+ newContents.put(file, new Date(file.lastModified()));
+ } else {
+ _listener.fileCreated(file);
+ newContents.put(file, new Date(file.lastModified()));
+ }
+ }
+ }
+
+ _contents = newContents;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Resource implemention for reading from a file.
+ *
+ * @author Erik Brakkee
+ */
+public class FileResource implements InputResource {
+ /**
+ * File to read.
+ */
+ private File _file;
+
+ /**
+ * Constructs the resource.
+ *
+ * @param aFile
+ * File to read.
+ */
+ public FileResource(File aFile) {
+ _file = aFile;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(_file));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a resource from which information can be read.
+ *
+ * @author Erik Brakkee
+ */
+public interface InputResource {
+ /**
+ * Gets the input stream to the resource. The obtained input stream must be
+ * closed once reading has finished.
+ *
+ * @return Input stream to the resource, never null.
+ * @throws IOException
+ * in case the resource cannot be found.
+ */
+ InputStream getInputStream() throws IOException;
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class SimpleProcess {
+
+ private static final Log LOG = LogFactory.getLog(SimpleProcess.class);
+
+ private File _directory;
+
+ private String[] _cmd;
+
+ private String _stdout;
+ private String _stderr;
+
+ public SimpleProcess(File aDirectory, String[] aCmd) {
+ _directory = aDirectory;
+ _cmd = aCmd;
+ }
+
+ /**
+ * @return the stdout
+ */
+ public String getStdout() {
+ return _stdout;
+ }
+
+ /**
+ * @return the stderr
+ */
+ public String getStderr() {
+ return _stderr;
+ }
+
+ /**
+ * Runs the process and blocks until it is done.
+ *
+ * @return Exit status of the process.
+ * @throws IOException
+ * In case of problems.
+ */
+ public int run() throws IOException {
+ return runImpl();
+ }
+
+ private int runImpl() throws IOException {
+ try {
+ String fullcmd = "";
+ for (String part: _cmd) {
+ fullcmd += " " + part;
+ }
+ LOG.debug("Executing '" + fullcmd + "' in directory '" + _directory
+ + "'");
+ java.lang.Process proc = Runtime.getRuntime().exec(_cmd, null, _directory);
+
+ // Read standard output and error in separate threads to avoid
+ // deadlock.
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+ Thread stdoutReader = readAndLogStream("STDOUT> ", proc
+ .getInputStream(), stdout);
+ Thread stderrReader = readAndLogStream("STDERR> ", proc
+ .getErrorStream(), stderr);
+
+ try {
+ proc.waitFor();
+ } catch (InterruptedException e) {
+ IOException exception = new IOException(
+ "Process was terminated: " + this);
+ exception.initCause(e);
+ throw exception;
+ }
+ waitForReader(stdoutReader);
+ waitForReader(stderrReader);
+
+ _stdout = stdout.toString();
+ _stderr = stderr.toString();
+
+ if (proc.exitValue() != 0) {
+ LOG.warn("Exit value was non-zero: " + this);
+ } else {
+ LOG.debug("Process finished");
+ }
+ return proc.exitValue();
+ } catch (IOException e) {
+ IOException exception = new IOException("Error executing process: "
+ + this);
+ exception.initCause(e);
+ throw exception;
+ }
+ }
+
+ private void waitForReader(Thread aReaderThread) {
+ try {
+ aReaderThread.join();
+ } catch (InterruptedException e) {
+ LOG
+ .warn(this
+ + ": error waiting for output stream reader of process to finish");
+ }
+ }
+
+ private Thread readAndLogStream(final String aPrefix,
+ final InputStream aStream, final Writer aOutput) {
+ Thread inputReader = new Thread() {
+ @Override
+ public void run() {
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new InputStreamReader(aStream));
+ String str;
+ while ((str = br.readLine()) != null) {
+ LOG.debug(aPrefix + str);
+ aOutput.write(str);
+ }
+ } catch (IOException e) {
+ LOG.warn(SimpleProcess.this + ": error reading input stream", e);
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (IOException e) {
+ LOG.warn("Error closing stream " + aPrefix);
+ }
+ }
+ }
+ }
+ };
+ inputReader.start();
+ return inputReader;
+ }
+
+ @Override
+ public String toString() {
+ String fullcmd = "";
+ for (String part: _cmd) {
+ fullcmd += part + " ";
+ }
+ return "process(dir = '" + _directory + "', cmd = '" + fullcmd + "')";
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input resource based on an input stream.
+ *
+ * @author Erik Brakkee
+ */
+public class StreamResource implements InputResource {
+ /**
+ * Input stream to read.
+ */
+ private InputStream _stream;
+
+ /**
+ * Constructs a resource.
+ *
+ * @param aStream
+ * Input stream to read.
+ */
+ public StreamResource(InputStream aStream) {
+ _stream = aStream;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see InputResource#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return _stream;
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides several support utilities for IO related functionality.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Default observer notifier which calls
+ * {@link org.wamblee.observer.Observer#send(ObservableType, Event)}
+ * immediately.
+ *
+ * @author Erik Brakkee
+ */
+public class DefaultObserverNotifier<ObservableType, Event> implements
+ ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Constructs the notifier.
+ *
+ */
+ public DefaultObserverNotifier() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.observer.ObserverNotifier#update(org.wamblee.observer.Observer,
+ * ObservableType, Event)
+ */
+ public void update(Observer<ObservableType, Event> aObserver,
+ ObservableType aObservable, Event aEvent) {
+ aObserver.send(aObservable, aEvent);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implements subscription and notification logic for an observer pattern. This
+ * class is thread safe.
+ */
+public class Observable<ObservableType, Event> {
+
+ private static final Logger LOGGER = Logger.getLogger(Observable.class);
+
+ /**
+ * Observable.
+ */
+ private ObservableType _observable;
+
+ /**
+ * Used to notify observers.
+ */
+ private ObserverNotifier<ObservableType, Event> _notifier;
+
+ /**
+ * Map of subscription to observer.
+ */
+ private Map<Long, Observer<ObservableType, Event>> _observers;
+
+ /**
+ * Counter for subscriptions. Holds the next subscription.
+ */
+ private long _counter;
+
+ /**
+ * Constructs the observable.
+ *
+ * @param aObservable
+ * Observable this instance is used for.
+ * @param aNotifier
+ * Object used for implementing notification of listeners.
+ */
+ public Observable(ObservableType aObservable,
+ ObserverNotifier<ObservableType, Event> aNotifier) {
+ _observable = aObservable;
+ _notifier = aNotifier;
+ _observers = new TreeMap<Long, Observer<ObservableType, Event>>();
+ _counter = 0;
+ }
+
+ /**
+ * Subscribe an obvers.
+ *
+ * @param aObserver
+ * Observer to subscribe.
+ * @return Event Event to send.
+ */
+ public synchronized long subscribe(Observer<ObservableType, Event> aObserver) {
+ long subscription = _counter++; // integer rage is so large it will
+ // never roll over.
+ _observers.put(subscription, aObserver);
+ return subscription;
+ }
+
+ /**
+ * Unsubscribe an observer.
+ *
+ * @param aSubscription
+ * Subscription which is used
+ * @throws IllegalArgumentException
+ * In case the subscription is not known.
+ */
+ public synchronized void unsubscribe(long aSubscription) {
+ Object obj = _observers.remove(aSubscription);
+ if (obj == null) {
+ throw new IllegalArgumentException("Subscription '" + aSubscription
+ + "'");
+ }
+ }
+
+ /**
+ * Gets the number of subscribed observers.
+ *
+ * @return Number of subscribed observers.
+ */
+ public int getObserverCount() {
+ return _observers.size();
+ }
+
+ /**
+ * Notifies all subscribed observers.
+ *
+ * @param aEvent
+ * Event to send.
+ */
+ public void send(Event aEvent) {
+ // Make sure we do the notification while not holding the lock to avoid
+ // potential deadlock
+ // situations.
+ List<Observer<ObservableType, Event>> observers = new ArrayList<Observer<ObservableType, Event>>();
+ synchronized (this) {
+ observers.addAll(_observers.values());
+ }
+ for (Observer<ObservableType, Event> observer : observers) {
+ _notifier.update(observer, _observable, aEvent);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#finalize()
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ if (_observers.size() > 0) {
+ LOGGER
+ .error("Still observers registered at finalization of observer!");
+ for (Observer observer : _observers.values()) {
+ LOGGER.error(" observer: " + observer);
+ }
+ }
+
+ super.finalize();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * This is a type-safe version of {@link java.util.Observable}.
+ *
+ * @author Erik Brakkee
+ */
+public interface Observer<ObservableType, Event> {
+
+ /**
+ * Called when an event has occurred on the observable.
+ * @param aObservable Observable.
+ * @param aEvent Event.
+ */
+ void send(ObservableType aObservable, Event aEvent);
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+/**
+ * Implementation of notification of subscribers.
+ *
+ * @author Erik Brakkee
+ */
+public interface ObserverNotifier<ObservableType, Event> {
+
+ /**
+ * Notifies an observer.
+ *
+ * @param aObserver Observer to notify
+ * @param aObservable Observable at which the event occured.
+ * @param aEvent Event that occured.
+ */
+ void update(Observer<ObservableType, Event> aObserver, ObservableType aObservable, Event aEvent);
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for the observer pattern.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Default implementation of Persistent.
+ *
+ * @author Erik Brakkee
+ */
+public abstract class AbstractPersistent implements Persistent {
+
+ /**
+ * Primary key.
+ */
+ private Serializable _primaryKey;
+
+ /**
+ * Version.
+ */
+ private int _version;
+
+ /**
+ * Constructs the object.
+ *
+ */
+ protected AbstractPersistent() {
+ _primaryKey = null;
+ _version = -1;
+ }
+
+ /**
+ * Copy constructor.
+ * @param aPersistent Object to copy.
+ */
+ protected AbstractPersistent(AbstractPersistent aPersistent) {
+ _primaryKey = aPersistent._primaryKey;
+ _version = aPersistent._version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPrimaryKey()
+ */
+ public Serializable getPrimaryKey() {
+ return _primaryKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPrimaryKey(java.io.Serializable)
+ */
+ public void setPrimaryKey(Serializable aKey) {
+ _primaryKey = aKey;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#getPersistedVersion()
+ */
+ public int getPersistedVersion() {
+ return _version;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.persistence.Persistent#setPersistedVersion(int)
+ */
+ public void setPersistedVersion(int aVersion) {
+ _version = aVersion;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence;
+
+import java.io.Serializable;
+
+/**
+ * Interface for persistent objects. This defines required functionality for all objects
+ * that are persisted.
+ *
+ * Objects that implement this interface and which implement
+ * {@link java.lang.Object#equals(java.lang.Object)}
+ * should exclude the primary key and version from determining equality.
+ */
+public interface Persistent {
+
+ /**
+ * Gets the primary key.
+ * @return Primary key.
+ * @see #setPrimaryKey(Serializable)
+ */
+ Serializable getPrimaryKey();
+
+ /**
+ * Sets the primary key.
+ * @param aKey Primary key.
+ * @see #getPrimaryKey()
+ */
+ void setPrimaryKey(Serializable aKey);
+
+ /**
+ * Gets the version.
+ * @return Version.
+ * @see #setPersistedVersion(int)
+ */
+ int getPersistedVersion();
+
+ /**
+ * Sets the version.
+ * @param aVersion Version.
+ * @see #getPersistedVersion()
+ */
+ void setPersistedVersion(int aVersion);
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support for persistence.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+package org.wamblee.reflection;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ReflectionUtils {
+
+ /**
+ * Wraps a type by the corresponding wrapper type if it is a primitive
+ * type.
+ * @param aClass Type to wrap.
+ * @return Wrapped type for primitives or the provided argument value.
+ */
+ public static Class wrapIfNeeded(Class aClass) {
+
+ if (aClass == boolean.class) {
+ return Boolean.class;
+ }
+ if (aClass == byte.class) {
+ return Byte.class;
+ }
+ if (aClass == char.class) {
+ return Character.class;
+ }
+ if (aClass == short.class) {
+ return Short.class;
+ }
+ if (aClass == int.class) {
+ return Integer.class;
+ }
+ if (aClass == long.class) {
+ return Long.class;
+ }
+ if (aClass == float.class) {
+ return Float.class;
+ }
+ if (aClass == double.class) {
+ return Double.class;
+ }
+ if (aClass == void.class) {
+ return Void.class;
+ }
+ return aClass;
+ }
+
+
+ public static List<Method> getAllMethods(Class aClass) {
+
+ Map<String,Method> found = new HashMap<String, Method>();
+ getAllMethods(aClass, found);
+ return new ArrayList<Method>(found.values());
+ }
+
+ private static void getAllMethods(Class aClass, Map<String,Method> aFound) {
+ List<Method> declared = Arrays.asList(aClass.getDeclaredMethods());
+ for (Method method: declared) {
+ Method superMethod = aFound.get(method.getName());
+ if ( superMethod == null ) {
+ // no superclass method
+ aFound.put(method.getName(), method);
+ }
+ else {
+ // super class method. Check for override.
+ if ( !Arrays.equals(superMethod.getParameterTypes(), method.getParameterTypes())) {
+ // parameters differ so this is a new method.
+ aFound.put(method.getName(), method);
+ }
+ }
+ }
+ Class superClass = aClass.getSuperclass();
+ if (superClass != null) {
+ getAllMethods(superClass, aFound);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * URI resolver that resolves stylesheets through the classpath.
+ */
+public class ClasspathUriResolver implements URIResolver {
+
+ /**
+ * Constructs the resolver.
+ *
+ */
+ public ClasspathUriResolver() {
+ // Empty.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.xml.transform.URIResolver#resolve(java.lang.String,
+ * java.lang.String)
+ */
+ public Source resolve(String aHref, String aBase)
+ throws TransformerException {
+ InputResource xslt = new ClassPathResource(aHref);
+ try {
+ return new StreamSource(xslt.getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(
+ "Could not get XSLT style sheet in classpath '" + aHref
+ + "'", e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+import org.dom4j.DocumentException;
+import org.dom4j.io.DOMReader;
+import org.dom4j.io.DOMWriter;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Some basic XML utilities for common reoccuring tasks for DOM documents.
+ *
+ * @author Erik Brakkee
+ */
+public final class DomUtils {
+
+ private static final Log LOG = LogFactory.getLog(DomUtils.class);
+
+ /**
+ * Disabled default constructor.
+ *
+ */
+ private DomUtils() {
+ // Empty.
+ }
+
+ /**
+ * Parses an XML document from a string.
+ *
+ * @param aDocument
+ * document.
+ * @return
+ */
+ public static Document read(String aDocument) throws XMLException {
+ ByteArrayInputStream is = new ByteArrayInputStream(aDocument.getBytes());
+ return read(is);
+ }
+
+ /**
+ * Parses an XML document from a stream.
+ *
+ * @param aIs
+ * Input stream.
+ * @return
+ */
+ public static Document read(InputStream aIs) throws XMLException {
+ try {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ return builder.parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+ }
+
+ /**
+ * Reads and validates a document against a schema.
+ *
+ * @param aIs
+ * Input stream.
+ * @param aSchema
+ * Schema.
+ * @return Parsed and validated document.
+ */
+ public static Document readAndValidate(InputStream aIs, InputStream aSchema)
+ throws XMLException {
+
+ try {
+ final Schema schema = SchemaFactory.newInstance(
+ XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(
+ new StreamSource(aSchema));
+
+ final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(true);
+ factory.setNamespaceAware(true);
+ factory.setSchema(schema);
+
+ return factory.newDocumentBuilder().parse(aIs);
+ } catch (SAXException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (IOException e) {
+ throw new XMLException(e.getMessage(), e);
+ } catch (ParserConfigurationException e) {
+ throw new XMLException(e.getMessage(), e);
+ } finally {
+ try {
+ aSchema.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing schema", e);
+ }
+ try {
+ aIs.close();
+ } catch (Exception e) {
+ LOG.warn("Error closing XML file", e);
+ }
+ }
+
+ }
+
+ /**
+ * Serializes an XML document to a stream.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @param aOs
+ * Output stream.
+ */
+ public static void serialize(Document aDocument, OutputStream aOs)
+ throws IOException {
+ XMLSerializer serializer = new XMLSerializer(aOs, new OutputFormat());
+ serializer.serialize(aDocument);
+ }
+
+ /**
+ * Serializes an XML document.
+ *
+ * @param aDocument
+ * Document to serialize.
+ * @return Serialized document.
+ */
+ public static String serialize(Document aDocument) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ serialize(aDocument, os);
+ return os.toString();
+ }
+
+ /**
+ * Converts a dom4j document into a w3c DOM document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return W3C DOM document.
+ */
+ public static Document convert(org.dom4j.Document aDocument)
+ throws DocumentException {
+ return new DOMWriter().write(aDocument);
+ }
+
+ /**
+ * Converts a W3C DOM document into a dom4j document.
+ *
+ * @param aDocument
+ * Document to convert.
+ * @return Dom4j document.
+ */
+ public static org.dom4j.Document convert(Document aDocument) {
+ return new DOMReader().read(aDocument);
+ }
+
+ /**
+ * Removes duplicate attributes from a DOM tree.This is useful for
+ * postprocessing the output of JTidy as a workaround for a bug in JTidy.
+ *
+ * @param aNode
+ * Node to remove duplicate attributes from (recursively).
+ * Attributes of the node itself are not dealt with. Only the
+ * child nodes are dealt with.
+ */
+ public static void removeDuplicateAttributes(Node aNode) {
+ NodeList list = aNode.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element) {
+ removeDuplicateAttributes((Element) node);
+ removeDuplicateAttributes(node);
+ }
+ }
+ }
+
+ /**
+ * Removes duplicate attributes from an element.
+ *
+ * @param aElement
+ * Element.
+ */
+ private static void removeDuplicateAttributes(Element aElement) {
+ NamedNodeMap attributes = aElement.getAttributes();
+ Map<String, Attr> uniqueAttributes = new TreeMap<String, Attr>();
+ List<Attr> attlist = new ArrayList<Attr>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ if (uniqueAttributes.containsKey(attribute.getNodeName())) {
+ LOG.info("Detected duplicate attribute (will be removed)'"
+ + attribute.getNodeName() + "'");
+ }
+ uniqueAttributes.put(attribute.getNodeName(), attribute);
+ attlist.add(attribute);
+ }
+ // Remove all attributes from the element.
+ for (Attr att : attlist) {
+ aElement.removeAttributeNode(att);
+ }
+ // Add the unique attributes back to the element.
+ for (Attr att : uniqueAttributes.values()) {
+ aElement.setAttributeNode(att);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+/**
+ * Exception thrown in case of XML parsing problems.
+ *
+ * @author Erik Brakkee
+ */
+public class XMLException extends Exception {
+
+ public XMLException(String aMsg) {
+ super(aMsg);
+ }
+
+ public XMLException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.Document;
+import org.wamblee.io.FileResource;
+
+/**
+ * XSL transformer for simplified usage of XSL transformations.
+ *
+ * @author Erik Brakkee
+ */
+public class XslTransformer {
+
+ private TransformerFactory _factory;
+
+ /**
+ * Constructs the URL resolver.
+ *
+ * @param aResolver
+ * URI resolver to use.
+ */
+ public XslTransformer(URIResolver aResolver) {
+ _factory = TransformerFactory.newInstance();
+ _factory.setURIResolver(aResolver);
+ }
+
+ /**
+ * Constructs the XSLT processor.
+ *
+ */
+ public XslTransformer() {
+ _factory = TransformerFactory.newInstance();
+ }
+
+ /**
+ * Resolves an XSLT based on URI.
+ * @param aXslt XSLT to resolve,
+ * @return Source for the XSLT
+ * @throws TransformerException In case the XSLT cannot be found.
+ */
+ public Source resolve(String aXslt) throws TransformerException {
+ URIResolver resolver = _factory.getURIResolver();
+ if (resolver == null) {
+ if (new File(aXslt).canRead()) {
+ try {
+ return new StreamSource(new FileResource(new File(aXslt))
+ .getInputStream());
+ } catch (IOException e) {
+ throw new TransformerException(e.getMessage(), e);
+ }
+ } else {
+ throw new TransformerException("Cannot read '" + aXslt + "'");
+ }
+ }
+ return resolver.resolve(aXslt, "");
+ }
+
+ /**
+ * Transforms a DOM document into another DOM document using a given XSLT
+ * transformation.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(Document aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new DOMSource(aDocument);
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSLT to use.
+ * @return Transformed document.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public Document transform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ DOMResult result = new DOMResult();
+ transform(source, result, aXslt);
+ return (Document) result.getNode();
+ }
+
+ /**
+ * Transforms a document to a text output. This supports XSLT
+ * transformations that result in text documents.
+ *
+ * @param aDocument
+ * Document to transform.
+ * @param aXslt
+ * XSL transformation.
+ * @return Transformed document.
+ */
+ public String textTransform(byte[] aDocument, Source aXslt)
+ throws IOException, TransformerException {
+ Source source = new StreamSource(new ByteArrayInputStream(aDocument));
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamResult result = new StreamResult(os);
+ transform(source, result, aXslt);
+ return new String(os.toByteArray());
+ }
+
+ /**
+ * Transforms a document using XSLT.
+ *
+ * @param aSource
+ * Document to transform.
+ * @param aResult
+ * Result of the transformation.
+ * @param aXslt
+ * XSLT to use.
+ * @throws IOException
+ * In case of problems reading resources.
+ * @throws TransformerException
+ * In case transformation fails.
+ */
+ public void transform(Source aSource, Result aResult, Source aXslt)
+ throws IOException, TransformerException {
+ try {
+ Transformer transformer = _factory.newTransformer(aXslt);
+ transformer.transform(aSource, aResult);
+ } catch (TransformerConfigurationException e) {
+ throw new TransformerException(
+ "Configuration problem of XSLT transformation", e);
+ }
+ }
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for XML processing.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+
+############################################################################################
+# Default configuration file for log4j.
+#
+# This properties file is used if no other configuration if log4j is done explicitly.
+############################################################################################
+
+
+# Root logger reports everything and uses the console appender
+log4j.rootLogger=ERROR, console
+
+# Log level for wamblee.org
+log4j.logger.org.wamblee=INFO
+log4j.logger.org.wamblee.usermgt.UserAdministrationImplTest=INFO
+log4j.logger.org.wamblee.security.authorization=ERROR
+log4j.logger.org.wamblee.cache=INFO
+log4j.logger.org.wamblee.crawler=DEBUG
+
+log4j.logger.org.springframework=ERROR
+log4j.logger.net.sf.ehcache=WARN
+
+# Default log level for hibernate
+log4j.logger.org.hibernate=ERROR
+log4j.logger.org.hibernate3=ERROR
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+######################################################################################
+# Hibernate SQL logging, switch the log level to DEBUG to see the output.
+######################################################################################
+
+log4j.logger.org.wamblee.test.SpringTestCase=ERROR, console
+log4j.additivity.org.wamblee.test.SpringTestCase=false
+
+# Logging for queries.
+log4j.logger.org.hibernate.SQL=ERROR, sql
+log4j.additivity.org.hibernate.SQL=false
+
+# Logging for query parameters and return values.
+log4j.logger.org.hibernate.type=ERROR, sqltype
+log4j.additivity.org.hibernate.type=false
+
+# Appender for the queries
+log4j.appender.sql=org.apache.log4j.ConsoleAppender
+log4j.appender.sql.layout=org.apache.log4j.PatternLayout
+log4j.appender.sql.layout.ConversionPattern=%n%-4r [%t] SQL: %x - %m%n
+
+# Appender to show the actual parameters and return values of the queries.
+log4j.appender.sqltype=org.apache.log4j.ConsoleAppender
+log4j.appender.sqltype.layout=org.apache.log4j.PatternLayout
+log4j.appender.sqltype.layout.ConversionPattern=%-4r [%t] SQL: %x - %m%n
+
+
+
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.cache;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import net.sf.ehcache.CacheException;
+
+import org.wamblee.io.TestResource;
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Cached object test.
+ *
+ * @author Erik Brakkee
+ */
+public class CachedObjectTest extends TestCase {
+
+ /**
+ *
+ */
+ private static final String EHCACHE_CONFIG = "ehcache.xml";
+
+ private static final int OBJECT_KEY = 10;
+
+ private CachedObject.Computation<Integer,Integer> _computation;
+ private int _ncomputations;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _computation = new CachedObject.Computation<Integer,Integer>() {
+ public Integer getObject(Integer aObjectKey) {
+ _ncomputations++;
+ return compute(aObjectKey);
+ };
+ };
+ _ncomputations = 0;
+ }
+
+ private int compute(int aValue) {
+ return aValue + 10;
+ }
+
+ private CachedObject<Integer, Integer> createCached(Cache<Integer,Integer> aCache) {
+ return new CachedObject<Integer, Integer>(aCache, OBJECT_KEY, _computation);
+ }
+
+ /**
+ * Verifies that upon first use, the cached object uses the computation to
+ * retrieve the object.
+ *
+ */
+ public void testComputation() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+
+ public void testInvalidateCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+ }
+
+ public void testBehaviorEhCache() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "test");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // explicit invalidation.
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(3, _ncomputations);
+
+ }
+
+ public void testBehaviorEhCacheDefault() throws CacheException, IOException {
+ Cache<Integer,Integer> cache = new EhCache<Integer,Integer>(new TestResource(CachedObjectTest.class, EHCACHE_CONFIG), "undefined");
+ CachedObject<Integer, Integer> cached = createCached(cache);
+
+ assertTrue( cache == cached.getCache());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ // The value must still be cached.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+
+ // Cache expiry.
+ TimingUtils.sleep(6000);
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ // Should still be cached now.
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(2, _ncomputations);
+
+ }
+
+
+ public void testBehaviorForeverCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ForeverCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ }
+ }
+
+ public void testBehaviorZeroCache() {
+ CachedObject<Integer, Integer> cached = createCached(new ZeroCache<Integer,Integer>());
+ int value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(1, _ncomputations);
+ for (int ncomp = 2; ncomp <= 100; ncomp++) {
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(ncomp, _ncomputations);
+ }
+ cached.invalidate();
+ value = cached.get();
+ assertEquals(compute(OBJECT_KEY), value);
+ assertEquals(101, _ncomputations);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.collections;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.wamblee.conditions.Condition;
+import org.wamblee.test.AssertionUtils;
+
+import junit.framework.TestCase;
+
+public class CollectionFilterTest extends TestCase {
+
+ public void testFilter() {
+ List<String> list = Arrays.asList(new String[] { "x", "y", "z", "y" });
+ List<String> result = new ArrayList<String>();
+ CollectionFilter.filter(list, result,
+ new Condition<String>() {
+ @Override
+ public boolean matches(String aObject) {
+ return aObject.equals("y");
+ }
+ });
+ AssertionUtils.assertEquals(new String[] { "y", "y" }, result.toArray());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.EventTracker;
+import org.wamblee.test.TimingUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the JVMLock.
+ *
+ * @author Erik Brakkee
+ */
+public abstract class AbstractLockTestCase extends TestCase {
+
+ protected static final int SLEEP_TIME = 1000;
+
+ protected static final String STARTED = "started";
+
+ protected static final String ACQUIRED = "acquired";
+
+ protected static final String RELEASED = "released";
+
+ private EventTracker<String> _tracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _tracker = new EventTracker<String>();
+ }
+
+ protected EventTracker<String> getTracker() {
+ return _tracker;
+ }
+
+ /**
+ * Must be implemented to generate the events
+ * {@link #STARTED}, {@link #ACQUIRED}, and {@link #RELEASED} in
+ * that order. The lock should be acquired for
+ * the time specified by {@link #SLEEP_TIME}.
+ * @return Thread which does the work.
+ */
+ protected abstract Thread runThread();
+
+ /**
+ * Tests the operation of the lock.
+ */
+ public void testLock() throws InterruptedException {
+ Thread t1 = runThread();
+ Thread t2 = runThread();
+ TimingUtils.sleep(SLEEP_TIME / 10); // give threads a chance to start
+ // up.
+ assertEquals(2, _tracker.getEventCount(STARTED)); // both threads
+ // should have
+ // started.
+ assertEquals(1, _tracker.getEventCount(ACQUIRED)); // one thread has
+ // acquired the
+ // lock.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(ACQUIRED)); // now the other
+ // thread could also
+ // acquire the lock
+ assertEquals(1, _tracker.getEventCount(RELEASED)); // and the first
+ // thread has
+ // released it.
+ TimingUtils.sleep(SLEEP_TIME);
+ assertEquals(2, _tracker.getEventCount(RELEASED)); // both threads
+ // should be
+ // finished.
+ t1.join();
+ t2.join();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import org.wamblee.test.TimingUtils;
+
+/**
+ * Tests for the JVMLock.
+ *
+ * @author Erik Brakkee
+ */
+public class JvmLockTest extends AbstractLockTestCase {
+
+ private JvmLock _lock;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new JvmLock();
+ }
+
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ getTracker().eventOccurred(STARTED);
+ _lock.acquire();
+ getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ _lock.release();
+ getTracker().eventOccurred(RELEASED);
+ };
+ });
+ t.start();
+ return t;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+
+/**
+ * Testing the read-write lock class. Note: in case of problems, test cases
+ * could hang.
+ *
+ * @see ReadWriteLock
+ */
+public class ReadWriteLockTest extends TestCase {
+ /**
+ *
+ */
+ private static final int HALF_SECOND = 500;
+ /**
+ *
+ */
+ private static final int ONE_SECOND = 1000;
+ /**
+ *
+ */
+ private static final int TWO_SECONDS = 2000;
+ private ReadWriteLock _lock;
+ private int _nReaders;
+ private int _nWriters;
+
+ /**
+ * Constructor for ReadWriteLockTest.
+ *
+ * @param aName
+ */
+ public ReadWriteLockTest(String aName) {
+ super(aName);
+ }
+
+ private synchronized int getReaderCount() {
+ return _nReaders;
+ }
+
+ private synchronized int getWriterCount() {
+ return _nWriters;
+ }
+
+ synchronized void incrementReaderCount() {
+ _nReaders++;
+ }
+
+ synchronized void incrementWriterCount() {
+ _nWriters++;
+ }
+
+ synchronized void decrementReaderCount() {
+ _nReaders--;
+ }
+
+ synchronized void decrementWriterCount() {
+ _nWriters--;
+ }
+
+ /*
+ * @see TestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ _lock = new ReadWriteLock();
+ }
+
+ /*
+ * @see TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ _lock = null;
+ super.tearDown();
+ }
+
+ /**
+ * Acquire and release a read lock.
+ */
+ public void testRead() {
+ _lock.acquireRead();
+ _lock.releaseRead();
+ }
+
+ /**
+ * Acquire and release a write lock.
+ */
+ public void testWrite() {
+ _lock.acquireWrite();
+ _lock.releaseWrite();
+ }
+
+ /**
+ * Verify concurrent access by multiple readers is possible.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleReaders() throws InterruptedException {
+ Runnable runnable = new ReadLocker(_lock, this, TWO_SECONDS);
+
+ Thread t1 = new Thread(runnable);
+ t1.start();
+
+ Thread t2 = new Thread(runnable);
+ t2.start();
+ Thread.sleep(ONE_SECOND);
+ assertTrue("Not enough readers!", getReaderCount() == 2);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that only one writer at a time can acquire the write lock.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testSingleWriter() throws InterruptedException {
+ WriteLocker writer = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer);
+ Thread t2 = new Thread(writer);
+
+ t1.start();
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue("Wrong writer count: " + getWriterCount(),
+ getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that multiple writers cannot acquire the write lock concurrently.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testMultipleWriters() throws InterruptedException {
+ WriteLocker writer1 = new WriteLocker(_lock, this, HALF_SECOND + ONE_SECOND);
+ WriteLocker writer2 = new WriteLocker(_lock, this, ONE_SECOND);
+ Thread t1 = new Thread(writer1);
+ Thread t2 = new Thread(writer2);
+
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1); // first writer still
+
+ // has the lock.
+ Thread.sleep(ONE_SECOND);
+
+ // at t = 2, the second writer still must have
+ // a lock.
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that after the first reader acquires a lock, a subsequent writer
+ * can only acquire the lock after the reader has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite1() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, reader still holding the
+ // lock so write lock cannot be acquired.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, read lock released and
+ // write lock must be acquired.
+ assertTrue("Wrong no. of readers: " + getReaderCount(),
+ getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /**
+ * Verify that when multiple readers have acquired a read lock, the writer
+ * can only acquire the lock after all readers have released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite2() throws InterruptedException {
+ ReadLocker readLocker1 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ ReadLocker readLocker2 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+ Thread t1 = new Thread(readLocker1);
+ Thread t2 = new Thread(readLocker2);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t3 = new Thread(writeLocker);
+
+ t1.start(); // acquire read lock [0, 2.5]
+ Thread.sleep(ONE_SECOND);
+ // t = 1
+ assertTrue(getReaderCount() == 1);
+ t2.start(); // acquire read lock [1, 3.5]
+ Thread.sleep(HALF_SECOND);
+ // t = 1.5
+ assertTrue(getReaderCount() == 2);
+ t3.start(); // write lock
+ Thread.sleep(HALF_SECOND);
+
+ // 2 seconds,
+ assertTrue(getReaderCount() == 2);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND);
+
+ // 3 seconds underway, first read lock must
+ // have been released.
+ assertTrue(getReaderCount() == 1);
+ assertTrue(getWriterCount() == 0);
+ Thread.sleep(ONE_SECOND);
+
+ // 4 seconds underway, write lock must have
+ // been acquired.
+ assertTrue(getReaderCount() == 0);
+ assertTrue(getWriterCount() == 1);
+
+ t1.join();
+ t2.join();
+ t3.join();
+ }
+
+ /**
+ * Verify that after a writer acquires a lock, a subsequent reader can
+ * only acquire the lock after the writer has released it.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReadWrite3() throws InterruptedException {
+ ReadLocker readLocker = new ReadLocker(_lock, this, TWO_SECONDS);
+ Thread t1 = new Thread(readLocker);
+ WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+ Thread t2 = new Thread(writeLocker);
+
+ t2.start(); // acquire write lock
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getWriterCount() == 1);
+ t1.start();
+ Thread.sleep(HALF_SECOND);
+
+ // 1 second underway, writer still holding the
+ // lock so read lock cannot be acquired.
+ assertTrue(getWriterCount() == 1);
+ assertTrue(getReaderCount() == 0);
+ Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+ // 2.5 seconds underway, write lock released and
+ // read lock must be acquired.
+ assertTrue("Wrong no. of writers: " + getReaderCount(),
+ getWriterCount() == 0);
+ assertTrue(getReaderCount() == 1);
+ t1.join();
+ t2.join();
+ }
+
+ /*
+ * The following test cases are for testing whether or not
+ * the read write lock checks the locking correctly.
+ * Strictly speaking, these checks wouldn't be necessary
+ * because it involves the contract of the ReadWriteLock which
+ * must be obeyed by users of the ReadWriteLock. Nevertheless,
+ * this is tested anyway to be absolutely sure.
+ */
+
+ /**
+ * Acquire a read lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseReadFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireRead();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseRead(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock from one thread, release it from another. Verify
+ * that a RuntimeException is thrown.
+ *
+ * @throws InterruptedException May not occur.
+ */
+ public void testReleaseWriteFromWrongThread() throws InterruptedException {
+ Thread t1 = null;
+
+ try {
+ t1 = new Thread(new Runnable() {
+ public void run() {
+ ReadWriteLockTest.this._lock.acquireWrite();
+ }
+ });
+ t1.start();
+ Thread.sleep(ONE_SECOND); // wait until thread is started
+ _lock.releaseWrite(); // release lock from wrong thread.
+ } catch (RuntimeException e) {
+ return; // ok
+ } finally {
+ t1.join();
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a read lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadTwice() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ // ok
+ return;
+ }
+
+ fail();
+ }
+
+ /**
+ * Try to acquire a write lock multiple times. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteTwice() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for reading and directly afterwards acquire it for
+ * writing. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.acquireWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire the lock for writing and directly afterwards acquire it for
+ * reading. Verify that a RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.acquireRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a read lock and release it as a write lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireReadFollowedByReleaseaWrite() {
+ try {
+ _lock.acquireRead();
+ _lock.releaseWrite();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+
+ /**
+ * Acquire a write lock and release it as a read lock. Verify that a
+ * RuntimeException is thrown.
+ */
+ public void testAcquireWriteFollowedByReleaseRead() {
+ try {
+ _lock.acquireWrite();
+ _lock.releaseRead();
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+
+ fail();
+ }
+}
+
+
+/**
+ * ReadLocker acquires a read lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the read
+ * lock, and performs a callback after the lock has been released.
+ */
+class ReadLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireRead();
+ _lockTest.incrementReaderCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("ReadLocker thread was interrupted."
+ + Thread.currentThread());
+ }
+
+ _lock.releaseRead();
+ _lockTest.decrementReaderCount();
+ }
+}
+
+
+/**
+ * WriteLocker acquires a write lock and performs a callback when the lock as
+ * been acquired, sleeps for a designated amount of time, releases the write
+ * lock, and performs a callback after the lock has been released.
+ */
+class WriteLocker implements Runnable {
+ private ReadWriteLock _lock;
+ private ReadWriteLockTest _lockTest;
+ private int _sleepTime;
+
+ public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+ int aSleepTime) {
+ _lock = aLock;
+ _lockTest = aLockTest;
+ _sleepTime = aSleepTime;
+ }
+
+ public void run() {
+ _lock.acquireWrite();
+ _lockTest.incrementWriterCount();
+
+ try {
+ Thread.sleep(_sleepTime);
+ } catch (InterruptedException e) {
+ Assert.fail("WriteLocker thread was interrupted: "
+ + Thread.currentThread());
+ }
+
+ _lock.releaseWrite();
+ _lockTest.decrementWriterCount();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ *
+ * @author Erik Brakkee
+ */
+public class AndConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ AndCondition<Integer> and = new AndCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, and.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ AndCondition<Integer> and = new AndCondition<Integer>(conditions);
+ assertEquals(aResult, and.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, false);
+ checkResult(false, true, false);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, false);
+ checkResult(new boolean[]{ false, true, false }, false);
+ checkResult(new boolean[]{ false, false, true }, false);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ *
+ * @author Erik Brakkee
+ */
+public class GreaterThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public GreaterThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject > _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ * Test condition object.
+ *
+ * @author Erik Brakkee
+ */
+public class LessThanCondition implements Condition<Integer> {
+
+ private int _value;
+
+ public LessThanCondition(int aValue) {
+ _value = aValue;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.conditions.Condition#matches(T)
+ */
+ public boolean matches(Integer aObject) {
+ return aObject < _value;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the Or Condition.
+ *
+ * @author Erik Brakkee
+ */
+public class OrConditionTest extends TestCase {
+
+ public void checkResult(boolean aFirst, boolean aSecond, boolean aResult) {
+ OrCondition<Integer> or = new OrCondition<Integer>(new FixedCondition<Integer>(aFirst),
+ new FixedCondition<Integer>(aSecond));
+ assertEquals(aResult, or.matches(0));
+ }
+
+ public void checkResult(boolean[] aValues, boolean aResult) {
+ List<Condition<Integer>> conditions = new ArrayList<Condition<Integer>>();
+ for (boolean value: aValues) {
+ conditions.add(new FixedCondition<Integer>(value));
+ }
+ OrCondition<Integer> or = new OrCondition<Integer>(conditions);
+ assertEquals(aResult, or.matches(new Integer(0)));
+ }
+
+ /**
+ * Checks all combinations of two conditions.
+ *
+ */
+ public void testTwoConditions() {
+ checkResult(false, false, false);
+ checkResult(true, false, true);
+ checkResult(false, true, true);
+ checkResult(true, true, true);
+ }
+
+ public void testMultipleConditions() {
+ checkResult(new boolean[]{ false, false, false} , false);
+ checkResult(new boolean[]{ true, false, false }, true);
+ checkResult(new boolean[]{ false, true, false }, true);
+ checkResult(new boolean[]{ false, false, true }, true);
+ checkResult(new boolean[]{ true, true, true }, true);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link org.wamblee.conditions.PropertyRegexCondition}.
+ *
+ * @author Erik Brakkee
+ */
+public class PropertyRegexConditionTest extends TestCase {
+
+ private boolean match(String aProperty, String aRegex, boolean aToLower, TestBean aBean) {
+ PropertyRegexCondition<TestBean> condition = new PropertyRegexCondition<TestBean>(aProperty, aRegex, aToLower );
+ return condition.matches(aBean);
+ }
+
+ private void checkMatch(String aProperty, String aRegex, boolean aToLower, TestBean aBean, boolean aResult) {
+ assertEquals( aResult, match(aProperty, aRegex, aToLower, aBean));
+ }
+
+ /**
+ * Verifies correct matching behavior for several cases.
+ *
+ */
+ public void testMatchProperty() {
+ TestBean bean = new TestBean("Hallo");
+ checkMatch("value", "Hallo", false, bean, true);
+ checkMatch("value", "all", false, bean, false);
+ checkMatch("value", ".a.*o", false, bean, true);
+ checkMatch("value", "hallo", false, bean, false); // no match when not converting to lower case.
+ checkMatch("value", "hallo", true, bean, true); // match!
+ }
+
+ /**
+ * Uses property regex condition for non-existing property.
+ * Verifies that a runtime exception is thrown.
+ *
+ */
+ public void testWrongProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("bla", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Applies condition to a private property. Verifies that a runtime
+ * exception is thrown.
+ *
+ */
+ public void testPrivateProperty() {
+ TestBean bean = new TestBean("Hallo");
+ try {
+ match("privateValue", ".*", false, bean);
+ } catch (RuntimeException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.conditions;
+
+/**
+ *
+ *
+ * @author Erik Brakkee
+ */
+public class TestBean {
+
+ private String _value;
+
+ public TestBean(String aValue) {
+ _value = aValue;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ private String getPrivateValue() {
+ return _value;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the bean kernel. The lookup of the bean factory itself can be tested
+ * only partially. Using a global property file for all test cases would tie all
+ * test cases together therefore no global property file is used.
+ *
+ * @author Erik Brakkee
+ */
+public class BeanKernelTest extends TestCase {
+
+ /**
+ * Loads the bean factory based on a property file configuration. Verifies
+ * the correct bean factory is loaded.
+ *
+ */
+ public void testLoadBeanFactoryFromProperties() {
+ BeanFactory factory = BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel.properties");
+ assertTrue(factory instanceof TestBeanFactory);
+ }
+
+ /**
+ * Loads the bean factory based on a non-existing property file.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentPropertyFile() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-nonexistent.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Loads the bean factory based on a property file with a non-existing
+ * bean factory defined in it.
+ * Verifies that BeanFactoryException is thrown.
+ *
+ */
+ public void testNonExistentBeanFactory() {
+ try {
+ BeanKernel
+ .lookupBeanFactory("org/wamblee/general/beankernel-wrong.properties");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Retrieves a bean factory throug the bean kernel. Verifies that beans can
+ * be retrieved.
+ *
+ */
+ public void testRetrieveFactory() {
+ BeanKernel.overrideBeanFactory(new TestBeanFactory()); // bypass
+ // default
+ // property
+ // lookup
+ BeanFactory factory = BeanKernel.getBeanFactory();
+ assertNotNull(factory);
+ assertEquals("hello", factory.find(String.class));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the pair class.
+ */
+public class PairTest extends TestCase {
+
+ public void testPair() {
+ Pair<Integer, String> pair = new Pair<Integer, String>(10, "hello");
+ assertEquals(new Integer(10), pair.getFirst());
+ assertEquals("hello", pair.getSecond());
+
+ Pair<Integer, String> pair2 = new Pair<Integer, String>(pair);
+ assertEquals(new Integer(10), pair2.getFirst());
+ assertEquals("hello", pair2.getSecond());
+
+
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.general;
+
+public class TestBeanFactory implements BeanFactory {
+
+ @Override
+ public Object find(String aId) {
+ return null;
+ }
+
+ @Override
+ public <T> T find(Class<T> aClass) {
+ if ( aClass.equals(String.class)) {
+ return (T)"hello";
+ }
+ return null;
+ }
+
+ @Override
+ public <T> T find(String aId, Class<T> aClass) {
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the classpath resource.
+ */
+public class ClassPathResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource from the class path. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource.txt");
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource from the class path. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ ClassPathResource resource = new ClassPathResource(
+ "org/wamblee/io/myresource-nonexistent.txt");
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import static org.mockito.Mockito.*;
+import junit.framework.TestCase;
+
+import org.apache.oro.io.AwkFilenameFilter;
+
+public class DirectoryMonitorTest extends TestCase {
+
+ private static final String REGEX = "^.*\\.txt$";
+ private static final String FILE1 = "file1.txt";
+
+ private TestData _data;
+ private DirectoryMonitor.Listener _listener;
+
+ private DirectoryMonitor _monitor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _data = new TestData(this);
+ _data.clean();
+ _listener = mock(DirectoryMonitor.Listener.class);
+ _monitor = new DirectoryMonitor(_data.getRoot(), new AwkFilenameFilter(
+ REGEX), _listener);
+ }
+
+ public void testEmptyDir() {
+ // Nothing is expected to be called.
+ for (int i = 0; i < 10; i++) {
+ _monitor.poll();
+ verifyNoMoreInteractions(_listener);
+ }
+ }
+
+ public void testFileCreated() {
+ _data.createFile(FILE1, "hello");
+ _monitor.poll();
+ verify(_listener).fileCreated(_data.getFile(FILE1));
+ }
+
+ public void testFileDeleted() {
+ _data.createFile(FILE1, "hello");
+ _monitor.poll();
+ reset(_listener);
+
+ _data.deleteFile(FILE1);
+ _monitor.poll();
+
+ verify(_listener).fileDeleted(_data.getFile(FILE1));
+ verifyNoMoreInteractions(_listener);
+ }
+
+ public void testFileChanged() throws InterruptedException {
+ _data.createFile(FILE1, "hello");
+ _monitor.poll();
+ reset(_listener);
+
+ Thread.sleep(2000);
+ _data.deleteFile(FILE1);
+ _data.createFile(FILE1, "bla");
+
+ _monitor.poll();
+ verify(_listener).fileChanged(_data.getFile(FILE1));
+ verifyNoMoreInteractions(_listener);
+ }
+
+ public void testFileFilterIsUsed() {
+ _monitor.poll();
+
+ _data.createFile("file.xml", "hello");
+ _monitor.poll();
+ verifyNoMoreInteractions(_listener);
+ }
+
+ public void testDirectoryIsIgnored() {
+ _monitor.poll();
+ _data.createDir(FILE1);
+ _monitor.poll();
+ verifyNoMoreInteractions(_listener);
+ }
+
+ public void testExceptionsWIllLeadToRepeatedNotifications() {
+ _monitor.poll();
+ _data.createFile(FILE1, "hello");
+
+ stubVoid(_listener).toThrow(new RuntimeException()).on().
+ fileCreated(_data.getFile(FILE1));
+
+ try {
+ _monitor.poll();
+ } catch (RuntimeException e) {
+ reset(_listener);
+
+ // polling again should lead to the same filecreated call.
+ // this time no exception is thrown.
+
+ _monitor.poll();
+ verify(_listener).fileCreated(_data.getFile(FILE1));
+ verifyNoMoreInteractions(_listener);
+ return;
+ }
+ fail(); // should not get here.
+
+
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+
+import org.apache.oro.io.AwkFilenameFilter;
+
+/**
+ *
+ *
+ * @author Erik Brakkee
+ */
+public class DirectoryMonitorTestProgram {
+
+ public static void main(String[] aArgs) throws Exception {
+
+ DirectoryMonitor monitor = new DirectoryMonitor(new File("."),
+ new AwkFilenameFilter(".*\\.txt"), new DirectoryMonitor.Listener() {
+ public void fileChanged(File aFile) {
+ System.out.println("changed " + aFile);
+ }
+ public void fileCreated(File aFile) {
+ System.out.println("created " + aFile);
+ }
+ public void fileDeleted(File aFile) {
+ System.out.println("deleted " + aFile);
+ }
+ });
+
+ for (;;) {
+ monitor.poll();
+ Thread.sleep(1000);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the file resource.
+ *
+ * @author Erik Brakkee
+ */
+public class FileResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource.txt"));
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+ /**
+ * Loads a non-existing resource. Verifies that an IO
+ * exception is thrown.
+ *
+ */
+ public void testResourceNotFound() {
+ try {
+ FileResource resource = new FileResource( new File(
+ FileSystemUtils.getTestInputDir(FileResourceTest.class),
+ "myresource-nonexistent.txt"));
+ InputStream is = resource.getInputStream();
+ } catch (IOException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.CodeSource;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * File system utilities.
+ *
+ * @author Erik Brakkee
+ */
+public final class FileSystemUtils {
+
+ private static final Log LOG = LogFactory.getLog(FileSystemUtils.class);
+
+ /**
+ * Test output directory relative to the sub project.
+ */
+ private static final String TEST_OUTPUT_DIR = "../target/testoutput";
+
+ /**
+ * Test input directory relative to the sub project.
+ */
+ private static final String TEST_INPUT_DIR = "../src/test/resources";
+
+ /*
+ * Disabled.
+ *
+ */
+ private FileSystemUtils() {
+ // Empty
+ }
+
+ /**
+ * Deletes a directory recursively. The test case will fail if the directory
+ * does not exist or if deletion fails.
+ *
+ * @param aDir
+ * Directory to delete.
+ */
+ public static void deleteDirRecursively(String aDir) {
+ deleteDirRecursively(new File(aDir));
+ }
+
+ /**
+ * Deletes a directory recursively. See {@link #deleteDirRecursively}.
+ *
+ * @param aDir
+ * Directory.
+ */
+ public static void deleteDirRecursively(File aDir) {
+ TestCase.assertTrue(aDir.isDirectory());
+
+ for (File file : aDir.listFiles()) {
+ if (file.isDirectory()) {
+ deleteDirRecursively(file);
+ } else {
+ delete(file);
+ }
+ }
+
+ delete(aDir);
+ }
+
+ /**
+ * Deletes a file or directory. The test case will fail if the file or
+ * directory does not exist or if deletion fails. Deletion of a non-empty
+ * directory will always fail.
+ *
+ * @param aFile
+ * File or directory to delete.
+ */
+ public static void delete(File aFile) {
+ TestCase.assertTrue(aFile.delete());
+ }
+
+ /**
+ * Gets a path relative to a sub project. This utility should be used to
+ * easily access file paths within a subproject without requiring any
+ * specific Eclipse configuration.
+ *
+ * @param aRelativePath
+ * Relative path.
+ * @param aTestClass
+ * Test class.
+ */
+ public static File getPath(String aRelativePath, Class aTestClass) {
+ CodeSource source = aTestClass.getProtectionDomain().getCodeSource();
+ if (source == null) {
+ LOG.warn("Could not obtain path for '" + aRelativePath
+ + "' for class " + aTestClass
+ + ", using relative path as is");
+ return new File(aRelativePath);
+ }
+ URL location = source.getLocation();
+ String protocol = location.getProtocol();
+ if (!protocol.equals("file")) {
+ LOG.warn("protocol is not 'file': " + location);
+ return new File(aRelativePath);
+ }
+
+ String path = location.getPath();
+ try {
+ path = URLDecoder.decode(location.getPath(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // ignore it.. just don't decode
+ LOG.warn("Decoding path failed: '" + location.getPath() + "'", e);
+ }
+
+ return new File(new File(path).getParentFile(), aRelativePath);
+ }
+
+ /**
+ * Ensures that a directory hierarchy exists (recursively if needed). If it
+ * is not possible to create the directory, then the test case will fail.
+ *
+ * @param aDir
+ * Directory to create.
+ */
+ public static void createDir(File aDir) {
+ if (aDir.exists() && !aDir.isDirectory()) {
+ TestCase.fail("'" + aDir
+ + "' already exists and is not a directory");
+ }
+ if (aDir.exists()) {
+ return;
+ }
+ createDir(aDir.getParentFile());
+ TestCase.assertTrue("Could not create '" + aDir + "'", aDir.mkdir());
+ }
+
+ /**
+ * Creates a file in a directory.
+ * @param aDir Directory path. Will be created if it does not exist.
+ * @param aName Filename.
+ * @param aContents Contents.
+ */
+ public static void createFile(File aDir, String aName, InputStream aContents) {
+ createDir(aDir);
+ try {
+ OutputStream os = new FileOutputStream(new File(aDir, aName));
+ copyStream(aContents, os);
+ } catch (IOException e) {
+ e.printStackTrace();
+ TestCase.fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Gets the test output directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test output directory.
+ */
+ public static File getTestOutputDir(Class aTestClass) {
+ File file = getPath(TEST_OUTPUT_DIR, aTestClass);
+ String className = aTestClass.getName();
+ String classRelPath = className.replaceAll("\\.", "/");
+ return new File(file, classRelPath);
+ }
+
+ /**
+ * Gets the test input directory for a specific test class.
+ *
+ * @param aTestClass
+ * Test class.
+ * @return Test input directory.
+ */
+ public static File getTestInputDir(Class aTestClass) {
+ File file = getPath(TEST_INPUT_DIR, aTestClass);
+ String packageName = aTestClass.getPackage().getName();
+ String packagePath = packageName.replaceAll("\\.", "/");
+ return new File(file, packagePath);
+ }
+
+ /**
+ * Creates a directory hierarchy for the output directory of a test class if
+ * needed.
+ *
+ * @param aTestClass
+ * Test class
+ * @return Test directory.
+ */
+ public static File createTestOutputDir(Class aTestClass) {
+ File file = getTestOutputDir(aTestClass);
+ createDir(file);
+ return file;
+ }
+
+ /**
+ * Gets a test output file name. This returns a File object representing the
+ * output file and ensures that the directory where the file will be created
+ * already exists.
+ *
+ * @param aName
+ * Name of the file.
+ * @param aTestClass
+ * Test class.
+ * @return File.
+ */
+ public static File getTestOutputFile(String aName, Class aTestClass) {
+ File file = new File(getTestOutputDir(aTestClass), aName);
+ createDir(file.getParentFile());
+ return file;
+ }
+
+ public static String read(InputStream aIs) throws IOException {
+ try {
+ StringBuffer buffer = new StringBuffer();
+ int c;
+ while ((c = aIs.read()) != -1) {
+ buffer.append((char) c);
+ }
+ return buffer.toString();
+ } finally {
+ aIs.close();
+ }
+ }
+
+ /**
+ * Copies an input stream to an output stream.
+ *
+ * @param aIs
+ * Input stream.
+ * @param aOs
+ * Output stream.
+ */
+ public static void copyStream(InputStream aIs, OutputStream aOs) {
+ try {
+ int c;
+ while ((c = aIs.read()) != -1) {
+ aOs.write(c);
+ }
+ aIs.close();
+ aOs.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Recursively copy a directory.
+ *
+ * @param aSrc
+ * Source directory
+ * @param aTarget
+ * Target directory.
+ */
+ public static void copyDir(File aSrc, File aTarget) {
+ Assert.assertTrue(aSrc.isDirectory());
+ Assert.assertTrue(!aTarget.exists());
+
+ aTarget.mkdirs();
+
+ File[] files = aSrc.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ if (!file.getName().equals(".svn")) {
+ copyDir(new File(aSrc, file.getName()), new File(aTarget,
+ file.getName()));
+ }
+ } else {
+ copyFile(file, new File(aTarget, file.getName()));
+ }
+ }
+ }
+
+ /**
+ * Copy a file. If copying fails then the testcase will fail.
+ *
+ * @param aSrc
+ * Source file.
+ * @param aTarget
+ * Target file.
+ */
+ public static void copyFile(File aSrc, File aTarget) {
+
+ try {
+ FileInputStream fis = new FileInputStream(aSrc);
+ FileOutputStream fos = new FileOutputStream(aTarget);
+ FileChannel fcin = fis.getChannel();
+ FileChannel fcout = fos.getChannel();
+
+ // map input file
+
+ MappedByteBuffer mbb = fcin.map(FileChannel.MapMode.READ_ONLY, 0,
+ fcin.size());
+
+ // do the file copy
+ fcout.write(mbb);
+
+ // finish up
+
+ fcin.close();
+ fcout.close();
+ fis.close();
+ fos.close();
+ } catch (IOException e) {
+ Assert.assertTrue("Copying file " + aSrc.getPath() + " to "
+ + aTarget.getPath() + " failed.", false);
+ }
+ }
+
+ /**
+ * Remove all files within a given directory including the directory itself.
+ * This only attempts to remove regular files and not directories within the
+ * directory. If the directory contains a nested directory, the deletion
+ * will fail. The test case will fail if this fails.
+ *
+ * @param aDir
+ * Directory to remove.
+ */
+ public static void deleteDir(File aDir) {
+ cleanDir(aDir);
+ delete(aDir);
+ }
+
+ /**
+ * Remove all regular files within a given directory.
+ *
+ * @param outputDirName
+ */
+ public static void cleanDir(File aDir) {
+ if (!aDir.exists()) {
+ return; // nothing to do.
+ }
+ File[] entries = aDir.listFiles();
+ for (int i = 0; i < entries.length; i++) {
+ File file = entries[i];
+ if (file.isFile()) {
+ Assert.assertTrue("Could not delete " + entries[i].getPath(),
+ entries[i].delete());
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the stream resource.
+ *
+ * @author Erik Brakkee
+ */
+public class StreamResourceTest extends TestCase {
+
+ /**
+ * Loads an existing resource. Verifies it is found.
+ *
+ */
+ public void testResourceFound() throws IOException {
+ File file = new File(
+ FileSystemUtils.getTestInputDir(StreamResourceTest.class),
+ "myresource.txt");
+ assertTrue(file.canRead());
+ InputStream fileIs = new FileInputStream(file);
+ InputResource resource = new StreamResource(fileIs);
+ InputStream is = resource.getInputStream();
+ String data = FileSystemUtils.read(is);
+ assertEquals("This is my resource", data);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import junit.framework.Assert;
+
+/**
+ * TestData provides a convenient interface for managing test output files.
+ *
+ * @author Erik Brakkee
+ */
+public final class TestData {
+
+ private Object _testcase;
+ private File _root;
+
+ /**
+ * Test data to be constructed in the setUp of a test. {@link #clean()} must
+ * be called to make sure that this directory is empty before executing a
+ * test.
+ */
+ public TestData(Object aTestcase) {
+ _testcase = aTestcase;
+ _root = getTestRootDir(aTestcase);
+ FileSystemUtils.createDir(_root);
+ }
+
+ /**
+ * Obtain root directory of JUnit tests.
+ *
+ * @return Directory name.
+ */
+ private static File getTestRootDir(Object aTestcase) {
+ return FileSystemUtils.getTestOutputDir(aTestcase.getClass());
+ }
+
+ public void createFile(String aRelative, String aFile, InputStream aContents) {
+ FileSystemUtils
+ .createFile(new File(_root, aRelative), aFile, aContents);
+ }
+
+ public void createFile(String aFile, String aContents) {
+ createFile(".", aFile, aContents);
+ }
+
+ public void createFile(String aRelative, String aFile, String aContents) {
+ InputStream is = new ByteArrayInputStream(aContents.getBytes());
+ FileSystemUtils.createFile(new File(_root, aRelative), aFile, is);
+ }
+
+ public void deleteFile(String aFile) {
+ deleteFile(".", aFile);
+ }
+
+ public void deleteFile(String aRelative, String aFile) {
+ FileSystemUtils.delete(new File(_root, aFile));
+ }
+
+ /**
+ * Returns a temporary directory.
+ *
+ * @return Temporary directory.
+ */
+ public File getTmpDir() {
+ return new File(_root, "tmpdir");
+ }
+
+ /**
+ * Cleans up the test output directory.
+ */
+ public void clean() {
+ FileSystemUtils.deleteDirRecursively(_root);
+ FileSystemUtils.createDir(_root);
+ }
+
+ /**
+ * Recursively copy a directory contents to the test output directory.
+ *
+ * @param sSrc
+ * Source directory to copy.
+ */
+ public void copyDir(File aSrc) {
+ FileSystemUtils.copyDir(aSrc, _root);
+ }
+
+ /**
+ * Copies a classpath resource to a relative path in the test output
+ * directory.
+ *
+ * @param aResource
+ * Resource to copy.
+ * @param aRelativePath
+ * Relative path.
+ */
+ public void copyResource(String aResource, String aRelativePath) {
+ try {
+ InputStream is = new ClassPathResource(aResource).getInputStream();
+ FileOutputStream fos = new FileOutputStream(new File(_root,
+ aRelativePath));
+ FileSystemUtils.copyStream(is, fos);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Assert.fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Copies a resource to the root directory of the test output.
+ *
+ * @param aResource
+ * Resource.
+ */
+ public void copyResource(String aResource) {
+ String basename = new File(aResource).getName();
+ copyResource(aResource, basename);
+ }
+
+ public void createDir(String aRelative) {
+ FileSystemUtils.createDir(new File(_root, aRelative));
+ }
+
+ /**
+ * Deletes a file or directory relative to the test output root.
+ *
+ * @param aRelative
+ * Relative path. The testcase will fail if the file or directory
+ * cannot be removed.
+ */
+ public void delete(String aRelative) {
+ FileSystemUtils.delete(new File(_root, aRelative));
+ }
+
+ /**
+ * Deletes a directory including its contents.
+ *
+ * @param aRelative
+ * Relative path.
+ */
+ public void deleteDir(String aRelative) {
+ FileSystemUtils.deleteDir(new File(_root, aRelative));
+ }
+
+ /**
+ * Deletes a directory recursively.
+ *
+ * @param aRelative
+ * Relative path.
+ */
+ public void deleteDirRecursively(String aRelative) {
+ FileSystemUtils.deleteDir(new File(_root, aRelative));
+ }
+
+ /**
+ * Gets the root of the test output directory.
+ *
+ * @return Root of the test output.
+ */
+ public File getRoot() {
+ return _root;
+ }
+
+ /**
+ * Gets a file object for a relative path.
+ */
+ public File getFile(String aRelative) {
+ return new File(_root, aRelative);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.io;
+
+import java.io.File;
+
+
+/**
+ * Test resource for locating resources in the classpath.
+ */
+public class TestResource extends FileResource {
+
+ /**
+ * Test class name.
+ * @param aTestClass Test class.
+ * @param aName Name of the file to look for.
+ */
+ public TestResource(Class aTestClass, String aName) {
+ super(getFile(aTestClass, aName));
+ }
+
+ /**
+ * Computes the file path of the file to look for.
+ * @param aClass Test class name.
+ * @param aName Name of the file.
+ * @return File.
+ */
+ private static File getFile(Class aClass, String aName) {
+ File dir = FileSystemUtils.getTestInputDir(aClass);
+ return new File(dir, aName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.observer;
+
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mockito.InOrder;
+
+import junit.framework.TestCase;
+
+/**
+ * Test of the observer pattern implementation.
+ *
+ * @author Erik Brakkee
+ */
+public class ObservableTest extends TestCase {
+
+ private static final int SUBSCRIBER_COUNT = 100;
+
+ private static final String UPDATE = "send";
+
+ private Integer _observed;
+ private Observable<Integer, String> _observable;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _observed = new Integer(1);
+ _observable = new Observable<Integer, String>(_observed,
+ new DefaultObserverNotifier());
+ }
+
+ /**
+ * Tests subscription and notification of one subscriber.
+ */
+ public void testOneObserver() {
+ final Observer mockObserver = mock(Observer.class);
+ InOrder order = inOrder(mockObserver);
+
+ long subscription = _observable.subscribe(mockObserver);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ final String message = "hallo";
+ _observable.send(message);
+
+ order.verify(mockObserver).send(_observed, message);
+ verifyNoMoreInteractions(mockObserver);
+
+ _observable.unsubscribe(subscription);
+ assertEquals(0, _observable.getObserverCount());
+
+ _observable.send(message);
+ verifyNoMoreInteractions(mockObserver);
+ }
+
+ /**
+ * Subscribes many susbcribers and sends notifications to subscribers.
+ * Verifies that unique subscription number are returned. Also verifies that
+ * the correct subscribers are notfied.
+ */
+ public void testManySubscribers() {
+ final int nsubscribers = SUBSCRIBER_COUNT;
+ final Observer[] mocks = new Observer[nsubscribers];
+ final InOrder[] order = new InOrder[nsubscribers];
+
+ List<Long> subscriptions = new ArrayList<Long>();
+ for (int i = 0; i < nsubscribers; i++) {
+ mocks[i] = mock(Observer.class);
+ order[i] = inOrder(mocks[i]);
+ long subscription = _observable.subscribe(mocks[i]);
+ assertTrue(subscriptions.add(subscription));
+ }
+
+ assertEquals(nsubscribers, _observable.getObserverCount());
+
+ final String message = "hallo";
+
+ _observable.send(message);
+ for (int i = 0; i < nsubscribers; i++) {
+ order[i].verify(mocks[i]).send(_observed, message);
+ }
+
+ for (int i = nsubscribers / 2; i < nsubscribers; i++) {
+ _observable.unsubscribe(subscriptions.get(i));
+ }
+ assertEquals(nsubscribers - (nsubscribers - nsubscribers / 2),
+ _observable.getObserverCount());
+
+ final String message2 = "blabla";
+
+ _observable.send(message2);
+ for (int i = 0; i < nsubscribers / 2; i++) {
+ order[i].verify(mocks[i]).send(_observed, message2);
+ }
+ for (int i = nsubscribers/2; i < nsubscribers; i++) {
+ verifyNoMoreInteractions(mocks[i]);
+ }
+
+ }
+
+ /**
+ * Subscribes and then unsubscribes with a wrong id. Verifies that
+ * IllegalArgumentException is thrown.
+ *
+ */
+ public void testUnsubscribeWithWrongSubscription() {
+ Observer<Integer, String> observer = mock(Observer.class);
+
+ long subscription = _observable.subscribe(observer);
+
+ assertEquals(1, _observable.getObserverCount());
+
+ try {
+ _observable.unsubscribe(subscription + 1);
+ } catch (IllegalArgumentException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Useful assertions for use in test cases.
+ *
+ * @author Erik Brakkee
+ */
+public final class AssertionUtils {
+
+ private static final Log LOG = LogFactory.getLog(AssertionUtils.class);
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private AssertionUtils() {
+ // Empty
+ }
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aExpected
+ * Expected object array.
+ * @param aActual
+ * Actual object array.
+ */
+ public static <T> void assertEquals(T[] aExpected, T[] aActual) {
+ assertEquals("", aExpected, aActual);
+ }
+
+
+ /**
+ * Asserts that two object arrays are equal.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected array.
+ * @param aActual
+ * Actual array.
+ */
+ public static <T> void assertEquals(String aMsg, T[] aExpected,
+ T[] aActual) {
+ TestCase.assertEquals(aMsg + " expected " +
+ Arrays.asList(aExpected) + ", actual " +
+ Arrays.asList(aActual) + ": Array lengths ", aExpected.length,
+ aActual.length);
+
+ for (int i = 0; i < aExpected.length; i++) {
+ TestCase.assertEquals(aMsg + ": Element " + i, aExpected[i],
+ aActual[i]);
+ }
+ }
+
+
+ /**
+ * Asserts that two objects are equal, and in case the object is an Object[]
+ * delegates to {@link #assertEquals(String, Object[], Object[]).
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpected
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static <T> void assertEquals(String aMsg, T aExpected,
+ T aActual) {
+ if (aExpected instanceof Object[]) {
+ AssertionUtils.assertEquals(aMsg, (Object[]) aExpected,
+ (Object[]) aActual);
+
+ return;
+ }
+
+ TestCase.assertEquals(aMsg, aExpected, aActual);
+ }
+
+ /**
+ * Asserts that two maps are equal by comparing all keys and by checking
+ * that the values for the same keys are the same.
+ *
+ * @param aMsg
+ * Message.
+ * @param aExpectedMap
+ * Expected result.
+ * @param aActual
+ * Actual result.
+ */
+ public static <Key,Value> void assertEquals(String aMsg,
+ Map<Key,Value> aExpectedMap, Map<Key,Value> aActual) {
+ TestCase.assertEquals("Map sizes differ", aExpectedMap.size(), aActual
+ .size());
+
+ Set keys = aExpectedMap.keySet();
+
+ for (Iterator i = keys.iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ TestCase.assertTrue("Map does not containg entry for key:" + key,
+ aActual.containsKey(key));
+ AssertionUtils.assertEquals("Value of key " + key + " of map",
+ aExpectedMap.get(key), aActual.get(key));
+ }
+ }
+
+ public static interface ErroneousCode {
+ void run() throws Exception;
+ }
+
+ /**
+ * Asserts that an exception occurs.
+ * @param aRunnable Test cases should create a subclass of this which contains the
+ * code that should throw an exception.
+ * @param aType Type of exception that is expected.
+ */
+ public static void assertException(ErroneousCode aObject, Class aType) {
+ try {
+ aObject.run();
+ throw new RuntimeException("No exception occurred");
+ } catch (Throwable t) {
+ if ( aType.isInstance(t)) {
+ LOG.info("Expected exception occured " + t.getMessage());
+ return; // ok
+ }
+ else {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Tracks the occurence of certain events in a test environment. Threads in a
+ * test environment tell the event tracker of the occurrence of certain events
+ * using {@link #eventOccurred(Event)}. Test code inspects the events sent by a
+ * thread using {@link #isEventSent(Thread, Event)}.
+ *
+ * A record is kept of every event which is sent. Therefore, the occurrence of a
+ * new event does not erase a previously sent event.
+ *
+ * @param <Event>
+ * Type of event sent from test code. Usually String will be
+ * sufficient. The event type must provide a sensible implementation
+ * of {@link java.lang.Object#equals(java.lang.Object)}.
+ *
+ * @author Erik Brakkee
+ */
+public class EventTracker<Event> {
+
+ private static final Log LOG = LogFactory.getLog(EventTracker.class);
+
+ /**
+ * Map of Thread object to a list of events.
+ */
+ private Map<Thread, List<Event>> _events;
+
+ /**
+ * Constructs the event tracker.
+ *
+ */
+ public EventTracker() {
+ clear();
+ }
+
+ public void clear() {
+ _events = new HashMap<Thread, List<Event>>();
+ }
+
+ /**
+ * Called by a thread to inform that an event has occurred.
+ *
+ * @param aEvent
+ * Event that was sent.
+ */
+ public synchronized void eventOccurred(Event aEvent) {
+ LOG.info("Event '" + aEvent + "' sent.");
+ Thread current = Thread.currentThread();
+ List<Event> events = _events.get(current);
+ if (events == null) {
+ events = new ArrayList<Event>();
+ _events.put(current, events);
+ }
+ events.add(aEvent);
+ }
+
+ /**
+ * Checks if a specific event has happened in a specific thread.
+ *
+ * @param aThread
+ * Thread to check.
+ * @param aEvent
+ * Event to check for.
+ * @return Whether or not the event was sent.
+ */
+ public synchronized boolean isEventSent(Thread aThread, Event aEvent) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ return false;
+ }
+ return events.contains(aEvent);
+ }
+
+ /**
+ * Gets the events for a thread in the order they were sent
+ *
+ * @param aThread
+ * Thread to get events for.
+ * @return Events that were sent. A zero-sized array is returned if no
+ * events were sent.
+ */
+ public synchronized List<Event> getEvents(Thread aThread) {
+ List<Event> events = _events.get(aThread);
+ if (events == null) {
+ events = Collections.emptyList();
+ }
+ return Collections.unmodifiableList(events);
+ }
+
+ /**
+ * Gets the number of times an event was sent summed up
+ * over all threads.
+ *
+ * @param aEvent
+ * Event to check.
+ * @return Number of times it was reached.
+ */
+ public synchronized int getEventCount(Event aEvent) {
+ int count = 0;
+ for (Thread thread : _events.keySet()) {
+ List<Event> events = _events.get(thread);
+ for (Event event : events) {
+ if (event.equals(aEvent)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Gets the total event count over all threads.
+ * @return
+ */
+ public synchronized int getEventCount() {
+ int count = 0;
+ for (Thread thread: _events.keySet()) {
+ count += _events.get(thread).size();
+ }
+ return count;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test;
+
+import junit.framework.TestCase;
+
+/**
+ * Timing utilities.
+ *
+ * @author Erik Brakkee
+ */
+public final class TimingUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private TimingUtils() {
+ // Empty
+ }
+
+ /**
+ * Sleeps for a time.
+ * @param aMillis Number of milliseconds to sleep.
+ */
+ public static void sleep(int aMillis) {
+ try {
+ Thread.sleep(aMillis);
+ } catch (InterruptedException e) {
+ TestCase.fail("Who interrupted my sleep?");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.IOException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.FileSystemUtils;
+
+/**
+ * Tests for {@link org.wamblee.xml.ClasspathUriResolver}.
+ *
+ * @author Erik Brakkee
+ */
+public class ClasspathUriResolverTest extends TestCase {
+
+ private URIResolver _resolver;
+
+ /* (non-Javadoc)
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ _resolver = new ClasspathUriResolver();
+ }
+
+ /**
+ * Resolves an existing file. Verifies the file is resolved correctly.
+ * @throws TransformerException
+ * @throws IOException
+ */
+ public void testResolveExistingFile() throws TransformerException, IOException {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml.xsl", "");
+ assertTrue(source instanceof StreamSource);
+ String resolved = FileSystemUtils.read(((StreamSource)source).getInputStream());
+
+ ClassPathResource resource = new ClassPathResource("org/wamblee/xml/reportToHtml.xsl");
+ String expected = FileSystemUtils.read(resource.getInputStream());
+ assertEquals(expected, resolved);
+ }
+
+ /**
+ * Resolves a non-existing file. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testResolveNonExistingFile() {
+ try {
+ Source source = _resolver.resolve("org/wamblee/xml/reportToHtml-nonexisting.xsl", "");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import junit.framework.TestCase;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.Element;
+
+/**
+ * XML test support utilities.
+ *
+ * @author Erik Brakkee
+ */
+public final class XmlUtils {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private XmlUtils() {
+ // Empty
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg,
+ org.w3c.dom.Document aExpected, org.w3c.dom.Document aActual) {
+ assertEquals(aMsg, DomUtils.convert(aExpected), DomUtils
+ .convert(aActual));
+ }
+
+ /**
+ * Checks equality of two XML documents excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Document aExpected,
+ Document aActual) {
+ assertEquals(aMsg + "/" + aExpected.getRootElement().getName(), aExpected.getRootElement(), aActual.getRootElement());
+ }
+
+ /**
+ * Checks equality of two XML elements excluding comment and processing
+ * nodes and trimming the text of the elements. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Element aExpected,
+ Element aActual) {
+
+ // Name.
+ TestCase.assertEquals(aMsg + "/name()", aExpected.getName(), aActual
+ .getName());
+
+ // Text
+ TestCase.assertEquals(aMsg + "/text()", aExpected.getTextTrim(),
+ aActual.getTextTrim());
+
+ // Attributes
+ List<Attribute> expectedAttrs = aExpected.attributes();
+ Collections.sort(expectedAttrs, new AttributeComparator());
+ List<Attribute> actualAttrs = aActual.attributes();
+ Collections.sort(actualAttrs, new AttributeComparator());
+
+ TestCase.assertEquals("count(" + aMsg + "/@*)", expectedAttrs.size(),
+ actualAttrs.size());
+ for (int i = 0; i < expectedAttrs.size(); i++) {
+ String msg = aMsg + "/@" + expectedAttrs.get(i).getName();
+ assertEquals(msg, expectedAttrs.get(i), actualAttrs.get(i));
+ }
+
+ // Nested elements.
+ List<Element> expectedElems = aExpected.elements();
+ List<Element> actualElems = aActual.elements();
+ TestCase.assertEquals("count(" + aMsg + "/*)", expectedElems.size(),
+ actualElems.size());
+ // determine the how-manyth element of the given name we are at.
+ // Maps element name to the last used index (or null if not yet used)
+ Map<String, Integer> elementIndex = new TreeMap<String, Integer>();
+ for (int i = 0; i < expectedElems.size(); i++) {
+ String elemName = expectedElems.get(i).getName();
+ Integer index = elementIndex.get(elemName);
+ if (index == null) {
+ index = 1;
+ } else {
+ index++;
+ }
+ elementIndex.put(elemName, index);
+ String msg = aMsg + "/" + expectedElems.get(i).getName() + "["
+ + index + "]";
+
+ assertEquals(msg, expectedElems.get(i), actualElems.get(i));
+ }
+ }
+
+ /**
+ * Checks equality of two attributes. In case of problems, it
+ * provides an xpath-like expression describing where the problem is.
+ *
+ * @param aMsg
+ * @param aExpected
+ * @param aActual
+ */
+ public static void assertEquals(String aMsg, Attribute aExpected,
+ Attribute aActual) {
+ TestCase.assertEquals(aMsg + ":name", aExpected.getName(), aActual
+ .getName());
+ TestCase.assertEquals(aMsg + ":value", aExpected.getValue(), aActual
+ .getValue());
+ }
+
+ /**
+ * Comparator which compares attributes by name.
+ */
+ private static final class AttributeComparator implements
+ Comparator<Attribute> {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.Comparator#compare(T, T)
+ */
+ public int compare(Attribute aAttribute1, Attribute aAttribute2) {
+ return aAttribute1.getName().compareTo(aAttribute2.getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.FileSystemUtils;
+import org.wamblee.io.InputResource;
+
+import org.w3c.dom.Document;
+
+/**
+ * Tests the XSL transformer.
+ *
+ * @author Erik Brakkee
+ */
+public class XslTransformerTest extends TestCase {
+
+ private static final String INCLUDED_XSL_FILE = "utilities.xsl";
+
+ private static final String REPORT_XML = "report.xml";
+
+ private static final String REPORT_TO_HTML_XSLT = "reportToHtml.xsl";
+
+ private static final String REPORT_TO_HTML2_XSLT = "reportToHtml2.xsl";
+
+ private static final String REPORT_TO_HTML_INVALID_XSLT = "reportToHtml-invalid.xsl";
+
+ private static final String REPORT_TO_HTML_NONWELLFORMED_XSLT = "reportToHtml-nonwellformed.xsl";
+
+ private static final String REPORT_TO_TEXT_XSLT = "reportToText.xsl";
+
+ private String getResourcePath(String aResource) {
+ return getClass().getPackage().getName().replaceAll("\\.", "/") + "/" + aResource;
+ }
+
+ /**
+ * Transforms a file while using the default resolver, where the included
+ * file can be found. Verifies the transformation is done correctly.
+ *
+ */
+ public void testTransformUsingDefaultResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new ClassPathResource(getResourcePath(REPORT_XML));
+
+ Source xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ Document document = builder.parse(xmlResource.getInputStream());
+ Source documentSource = new StreamSource(xmlResource.getInputStream());
+
+ Document expected = DomUtils.read(new ClassPathResource(getResourcePath(
+ "output-reportToHtml-report.xml")).getInputStream());
+
+ Document output1 = transformer.transform(documentData, xslt);
+ XmlUtils.assertEquals("byte[] transform", expected, output1);
+
+ xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+ Document output2 = transformer.transform(document, xslt);
+ XmlUtils.assertEquals("document transform", expected, output2);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Result output = new StreamResult(os);
+
+ xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+ transformer.transform(documentSource, output, xslt);
+ XmlUtils.assertEquals("document source transform", expected, DomUtils
+ .read(os.toString()));
+
+ xslt = new StreamSource(new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_XSLT)).getInputStream());
+ String result = transformer.textTransform(documentData, xslt);
+ XmlUtils
+ .assertEquals("text transform", expected, DomUtils.read(result));
+ }
+
+ /**
+ * Transforms a file using the default resolver where the included file
+ * cannot be found. Verifies that a TransformerException is thrown.
+ *
+ */
+ public void testTransformUsingDefaultResolverFails() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource =
+ new ClassPathResource(getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML2_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using an invalid Xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformInvalidXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new ClassPathResource(
+ getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(REPORT_TO_HTML_INVALID_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a non-well formed xslt. Verifies that a
+ * TransformerException is thrown.
+ *
+ */
+ public void testTransformNonWellformedXslt() throws IOException {
+ XslTransformer transformer = new XslTransformer();
+
+ InputResource xmlResource = new ClassPathResource(
+ getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(
+ REPORT_TO_HTML_NONWELLFORMED_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+ try {
+ Document output1 = transformer.transform(documentData, xslt);
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ /**
+ * Transforms a file using a class path resolver.
+ *
+ */
+ public void testTransformUsingClassPathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new ClassPathResource(getResourcePath(
+ REPORT_XML));
+ Source xslt = new StreamSource(new ClassPathResource(
+ getResourcePath(REPORT_TO_HTML2_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ Document output1 = transformer.transform(documentData, xslt);
+ Document expected = DomUtils.read(new ClassPathResource(
+ getResourcePath("output-reportToHtml-report.xml"))
+ .getInputStream());
+ XmlUtils.assertEquals("doc", expected, output1);
+ }
+
+ /**
+ * Transforms a file to text output. Verifies the file is transformed
+ * correctly.
+ *
+ */
+ public void testTransformToTextOutput() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+
+ InputResource xmlResource = new ClassPathResource(
+ getResourcePath(REPORT_XML));
+ Source xslt = new StreamSource(
+ new ClassPathResource(getResourcePath(REPORT_TO_TEXT_XSLT)).getInputStream());
+
+ byte[] documentData = FileSystemUtils
+ .read(xmlResource.getInputStream()).getBytes();
+
+ String result = transformer.textTransform(documentData, xslt);
+ String expected = "Hello world!";
+ assertEquals("text transform", expected, result);
+ }
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver where the file does not exist.
+ *
+ */
+ public void testResolveWithDefaultResolverFileNotFound() {
+ XslTransformer transformer = new XslTransformer();
+ try {
+ Source source = transformer.resolve("org/wamblee/xml/utilities-nonexistent.xsl");
+ } catch (TransformerException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+
+ /**
+ * Tests resolving a file using {@link XslTransformer#resolve(String)} with the
+ * default resolver.
+ *
+ */
+ public void testResolveWithClasspathResolver() throws Exception {
+ XslTransformer transformer = new XslTransformer(new ClasspathUriResolver());
+ Source source = transformer.resolve(getResourcePath(INCLUDED_XSL_FILE));
+ assert(source instanceof StreamSource);
+ StreamSource ssource = (StreamSource)source;
+ String data = FileSystemUtils.read(ssource.getInputStream());
+ String expected = FileSystemUtils.read(new ClassPathResource(getResourcePath(INCLUDED_XSL_FILE)).getInputStream());
+ assertEquals(expected, data);
+ }
+
+}
+
+
--- /dev/null
+<ehcache>
+
+ <!-- Sets the path to the directory where cache .data files are created.
+
+ If the path is a Java System Property it is replaced by
+ its value in the running VM.
+
+ The following properties are translated:
+ user.home - User's home directory
+ user.dir - User's current working directory
+ java.io.tmpdir - Default temp file path -->
+ <diskStore path="java.io.tmpdir"/>
+
+
+ <!--Default Cache configuration. These will applied to caches programmatically created through
+ the CacheManager.
+
+ The following attributes are required:
+
+ maxElementsInMemory - Sets the maximum number of objects that will be created in memory
+ eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the
+ element is never expired.
+ overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
+ has reached the maxInMemory limit.
+
+ The following attributes are optional:
+ timeToIdleSeconds - Sets the time to idle for an element before it expires.
+ i.e. The maximum amount of time between accesses before an element expires
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that an Element can idle for infinity.
+ The default value is 0.
+ timeToLiveSeconds - Sets the time to live for an element before it expires.
+ i.e. The maximum time between creation time and when an element expires.
+ Is only used if the element is not eternal.
+ Optional attribute. A value of 0 means that and Element can live for infinity.
+ The default value is 0.
+ diskPersistent - Whether the disk store persists between restarts of the Virtual Machine.
+ The default value is false.
+ diskExpiryThreadIntervalSeconds- The number of seconds between runs of the disk expiry thread. The default value
+ is 120 seconds.
+ -->
+
+ <defaultCache
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+ <cache
+ name="test"
+ maxElementsInMemory="10000"
+ eternal="false"
+ overflowToDisk="false"
+ timeToIdleSeconds="5"
+ timeToLiveSeconds="5"
+ diskPersistent="false"
+ diskExpiryThreadIntervalSeconds="120"
+ />
+
+</ehcache>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+ <beans>
+
+ <bean id="test"
+ class="org.springframework.context.support.ClassPathXmlApplicationContext">
+ <constructor-arg>
+ <list>
+ <value>org/wamblee/general/spring1.xml</value>
+ </list>
+ </constructor-arg>
+ </bean>
+
+ </beans>
\ No newline at end of file
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactoryBla
--- /dev/null
+
+org.wamblee.beanfactory.class=org.wamblee.general.TestBeanFactory
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans>
+
+ <bean id="java.lang.String"
+ class="java.lang.String">
+ <constructor-arg><value>hello</value></constructor-arg>
+ </bean>
+
+</beans>
\ No newline at end of file
--- /dev/null
+This is my resource
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <h2>Successfully recorded programs <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:25 - 00:10: <strong>Wintertijd</strong> (Nederland
+ 1/Documentaire)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Some description MINSK - De presidentsverkiezingen
+ in Wit-Rusland zijn zondag met ruime cijfers gewonnen door
+ zittend president Aleksandr Loekasjenko. Dat bleek zondag uit
+ exitpolls uitgevoerd in opdracht van het totalitaire regime. Het
+ staatshoofd zou kunnen rekenen op ruim 82 procent van de
+ stemmen. Volgens de eerste gedeeltelijke uitslagen zou
+ Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p></h2>
+ <h2>Possibly interesting programs</h2>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Brainiac</strong> (Discovery Channel/science)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Humor</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ <h3>Category: horror</h3>
+ <p>
+ <table align="left" cellpadding="5">
+ <tr align="left">
+ <td>23:30 - 00:15: <strong>Andere tijden</strong> (Nederland 1/docu)</td>
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">Documentaire</font>
+ </blockquote>
+ </td>
+ </tr>
+ </table>
+ <br clear="left"/>
+ </p>
+ </body>
+</html>
--- /dev/null
+<report>
+ <recorded result="OK">
+ <program>
+ <name>Wintertijd</name>
+ <description>Some description MINSK - De presidentsverkiezingen in Wit-Rusland zijn zondag met ruime cijfers gewonnen door zittend president Aleksandr Loekasjenko. Dat bleek zondag uit exitpolls uitgevoerd in opdracht van het totalitaire regime. Het staatshoofd zou kunnen rekenen op ruim 82 procent van de stemmen. Volgens de eerste gedeeltelijke uitslagen zou Loekasjenko zelfs kunnen rekenen op bijna 89 procent. </description>
+ <keywords>Documentaire</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:25</begin>
+ <end>00:10</end>
+ </interval>
+ </program>
+ </recorded>
+
+ <interesting>
+ <program>
+ <name>Brainiac</name>
+ <description>Humor</description>
+ <keywords>science</keywords>
+ <channel>Discovery Channel</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ <category name="horror">
+ <program>
+ <name>Andere tijden</name>
+ <description>Documentaire</description>
+ <keywords>docu</keywords>
+ <channel>Nederland 1</channel>
+ <interval>
+ <begin>23:30</begin>
+ <end>00:15</end>
+ </interval>
+ </program>
+ </category>
+
+ </interesting>
+
+</report>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifdd test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:ifdd>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:ifx test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+<xsl:include href="org/wamblee/xml/utilities.xsl"/>
+
+<xsl:output method="xml"/>
+ <xsl:template match="report">
+ <html>
+ <head>
+ <title>KiSS crawler report</title>
+ </head>
+ <body>
+ <h1>KiSS crawler report</h1>
+ <xsl:apply-templates select="recorded"/>
+ <xsl:if test="count(interesting) > 0">
+ <h2>Possibly interesting programs</h2>
+ <xsl:apply-templates select="interesting"/>
+ </xsl:if>
+ <xsl:if test="count(//program) = 0">
+ <xsl:text>No suitable programs found </xsl:text>
+ </xsl:if>
+ </body>
+ </html>
+
+ </xsl:template>
+
+ <xsl:template name="programTable">
+ <p>
+ <table align="left" cellpadding="5">
+ <!--
+ <tr align="left">
+ <th align="left">Time</th>
+ <th align="left">Channel</th>
+ <th align="left">Program</th>
+ </tr>
+ -->
+ <xsl:apply-templates select="program"/>
+ </table>
+ <br clear="left"/>
+ </p>
+ </xsl:template>
+
+ <xsl:template match="recorded">
+ <h2>
+ <xsl:choose>
+ <xsl:when test="@result = 'OK'">
+ <xsl:text>Successfully recorded programs </xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'DUPLICATE'">
+ <xsl:text>Already recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result = 'CONFLICT'">
+ <xsl:text>Conflicts with other recorded programs</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ <xsl:when test="@result='ERROR'">
+ <xsl:text>Programs that could not be recorded for
+ technical reasons.</xsl:text>
+ <xsl:call-template name="programTable"/>
+ </xsl:when>
+ </xsl:choose>
+ </h2>
+ </xsl:template>
+
+ <xsl:template name="addProgramInfo">
+ <tr align="left">
+ <td><xsl:value-of select="interval/begin"/> - <xsl:value-of select="interval/end"/>: <strong>
+ <xsl:value-of select="name"/>
+ </strong> (<xsl:value-of select="channel"/>/<xsl:value-of select="keywords"/>)</td>
+
+ </tr>
+ <tr>
+ <td>
+ <blockquote>
+ <font size="-1">
+ <xsl:value-of select="description"/>
+ </font>
+ </blockquote>
+ </td>
+
+
+ </tr>
+ </xsl:template>
+
+ <xsl:template match="program">
+ <xsl:call-template name="addProgramInfo"/>
+ </xsl:template>
+
+ <xsl:template match="interesting">
+ <xsl:call-template name="programTable"/>
+ <xsl:apply-templates select="category"/>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <h3>Category: <xsl:value-of select="@name"/></h3>
+ <xsl:call-template name="programTable"/>
+ </xsl:template>
+</xsl:stylesheet>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text"/>
+
+ <xsl:template match="report">
+ <xsl:text>Hello world!</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Note the declaration of the namespace for XInclude. -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+
+ <xsl:variable name="newline">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <xsl:variable name="carriageReturn">
+ <xsl:text> </xsl:text>
+ </xsl:variable>
+
+ <!-- =====================================================
+ Replace one string by another
+ - src: string to do substituion in
+ - from: literal string to replace
+ - to:substitution string.
+ ======================================================-->
+ <xsl:template name="string-replace">
+ <xsl:param name="src"/>
+ <xsl:param name="from"/>
+ <xsl:param name="to"/>
+ <xsl:choose>
+ <xsl:when test="contains($src, $from)">
+ <xsl:value-of select="substring-before($src, $from)"/>
+ <xsl:value-of select="$to"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src" select="substring-after($src, $from)"/>
+ <xsl:with-param name="from" select="$from"/>
+ <xsl:with-param name="to" select="$to"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$src"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <xsl:template name="indent">
+ <xsl:param name="src"/>
+ <xsl:param name="indentString"/>
+ <xsl:value-of select="$indentString"/>
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:value-of select="$newline"/>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$indentString"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap">
+ <xsl:param name="src"/>
+ <xsl:param name="width"/>
+
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:call-template name="string-replace">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$src"/>
+ </xsl:with-param>
+ <xsl:with-param name="from">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="to">
+ <xsl:text> </xsl:text>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="0"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="word-wrap-impl">
+ <xsl:param name="src"/>
+ <xsl:param name="index"/>
+ <xsl:param name="width"/>
+
+ <xsl:variable name="word">
+ <xsl:value-of select="substring-before($src, ' ')"/>
+ </xsl:variable>
+ <xsl:variable name="wordlength">
+ <xsl:value-of select="string-length($word)"/>
+ </xsl:variable>
+ <xsl:variable name="remainder">
+ <xsl:value-of select="substring($src, $wordlength+2)"/>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="$index + $wordlength + 1 > $width">
+ <xsl:value-of select="$newline"/>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$wordlength + 1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$word"/>
+ <xsl:text> </xsl:text>
+
+ <xsl:if test="string-length($remainder) > 0">
+ <xsl:call-template name="word-wrap-impl">
+ <xsl:with-param name="src">
+ <xsl:value-of select="$remainder"/>
+ </xsl:with-param>
+ <xsl:with-param name="index">
+ <xsl:value-of select="$index + $wordlength+1"/>
+ </xsl:with-param>
+ <xsl:with-param name="width">
+ <xsl:value-of select="$width"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support</artifactId>
+ <packaging>pom</packaging>
+ <name>/support</name>
+ <url>http://wamblee.org</url>
+
+ <modules>
+ <module>general</module>
+ <module>spring</module>
+ </modules>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-spring</artifactId>
+ <packaging>jar</packaging>
+ <name>/support/spring</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>0.2</version>
+ <type>test-jar</type>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-hibernate3</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-hibernate-jpa</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aop</artifactId>
+ </dependency>
+ <!-- should be possible to remove the dependence on log4j -->
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dom4j</groupId>
+ <artifactId>dom4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-hibernate-jpa</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency.spring;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.wamblee.concurrency.Lock;
+
+/**
+ * Locking advice. This automatically synchronized an object using a given lock.
+ *
+ * @author Erik Brakkee
+ */
+public class LockAdvice implements MethodInterceptor {
+
+ /**
+ * Lock to use.
+ */
+ private Lock _lock;
+
+ /**
+ * Constructs lock advice.
+ * @param aLock Lock to use.
+ */
+ public LockAdvice(Lock aLock) {
+ _lock = aLock;
+ }
+
+ /* (non-Javadoc)
+ * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
+ */
+ public Object invoke(MethodInvocation aInvocation) throws Throwable {
+ _lock.acquire();
+ try {
+ return aInvocation.proceed();
+ } finally {
+ _lock.release();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general.spring;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.access.BeanFactoryLocator;
+import org.springframework.beans.factory.access.BeanFactoryReference;
+import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.general.BeanFactoryException;
+import org.wamblee.general.BeanKernel;
+
+/**
+ * Bean factory which uses Spring. This bean factory cannot be configured
+ * directly in the {@link org.wamblee.general.BeanKernel} because it does not
+ * provide a default no-arg constructor. Therefore, it must be delegated to or
+ * it must tbe subclassed to provide a default constructor.
+ */
+public class SpringBeanFactory implements BeanFactory {
+
+ private BeanFactoryReference _factoryReference;
+
+ /**
+ * Constructs the bean factory.
+ *
+ * @param aSelector
+ * Selector to find the appropriate bean ref context.
+ * @param aFactoryName
+ * Spring bean factory to use.
+ */
+ public SpringBeanFactory(String aSelector, String aFactoryName) {
+ try {
+ BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator
+ .getInstance(aSelector);
+ _factoryReference = locator.useBeanFactory(aFactoryName);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(
+ "Could not load bean factory: selector = '" + aSelector
+ + "', factory = '" + aFactoryName + "'", e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String,
+ * java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _factoryReference.getFactory().getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Gets the spring bean factory.
+ * @return Spring bean factory.
+ */
+ public org.springframework.beans.factory.BeanFactory getSpringBeanFactory() {
+ return _factoryReference.getFactory();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Hibernate mapping files to use.
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateMappingFiles extends ArrayList<String> {
+
+ /**
+ * Constructs an empty list of hibernate mapping files.
+ *
+ */
+ public HibernateMappingFiles() {
+ super();
+ }
+
+ /**
+ * Constructs the list of Spring config files.
+ * @param aFiles Files.
+ */
+ public HibernateMappingFiles(String[] aFiles) {
+ super();
+ addAll(Arrays.asList(aFiles));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.persistence.hibernate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+import org.wamblee.persistence.Persistent;
+
+/**
+ * Extension of
+ * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
+ *
+ * @author Erik Brakkee
+ */
+public class HibernateSupport extends HibernateDaoSupport {
+
+ private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
+
+ /**
+ * This class provided an equality operation based on the object reference
+ * of the wrapped object. This is required because we cannto assume that the
+ * equals operation has any meaning for different types of persistent
+ * objects. This allows us to use the standard collection classes for
+ * detecting cyclic dependences and avoiding recursion.
+ *
+ */
+ private static final class ObjectElem {
+ private Object _object;
+
+ public ObjectElem(Object aObject) {
+ _object = aObject;
+ }
+
+ public boolean equals(Object aObj) {
+ return ((ObjectElem) aObj)._object == _object;
+ }
+
+ public int hashCode() {
+ return _object.hashCode();
+ }
+ }
+
+ /**
+ * Constructs the object.
+ *
+ */
+ public HibernateSupport() {
+ // Empty
+ }
+
+ /**
+ * Performes a hibernate <code>Session.merge()</code> and updates the
+ * object with the correct primary key and version. This is an extension to
+ * the hibernate merge operation because hibernate itself leaves the object
+ * passed to merge untouched.
+ *
+ * Use this method with extreme caution since it will recursively load all
+ * objects that the current object has relations with and for which
+ * cascade="merge" was specified in the Hibernate mapping file.
+ *
+ * @param aPersistent
+ * Object to merge.
+ */
+ public void merge(Persistent aPersistent) {
+ merge(getHibernateTemplate(), aPersistent);
+ }
+
+ /**
+ * As {@link #merge(Persistent)} but with a given template. This method can
+ * be accessed in a static way.
+ *
+ * @param aTemplate
+ * Hibernate template
+ * @param aPersistent
+ * Object to merge.
+ */
+ public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
+ Persistent merged = (Persistent) aTemplate.merge(aPersistent);
+ processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
+ }
+
+ /**
+ * Copies primary keys and version from the result of the merged to the
+ * object that was passed to the merge operation. It does this by traversing
+ * the properties of the object. It copies the primary key and version for
+ * objects that implement {@link Persistent} and applies the same rules to
+ * objects in maps and sets as well (i.e. recursively).
+ *
+ * @param aPersistent
+ * Object whose primary key and version are to be set.
+ * @param aMerged
+ * Object that was the result of the merge.
+ * @param aProcessed
+ * List of already processed Persistent objects of the persistent
+ * part.
+ */
+ public static void processPersistent(Persistent aPersistent,
+ Persistent aMerged, List<ObjectElem> aProcessed) {
+ if (aPersistent == null && aMerged == null) {
+ return;
+ }
+ if (aPersistent == null || aMerged == null) {
+ throw new RuntimeException("persistent or merged object is null '"
+ + aPersistent + "'" + " '" + aMerged + "'");
+ }
+ ObjectElem elem = new ObjectElem(aPersistent);
+ if (aProcessed.contains(elem)) {
+ return; // already processed.
+ }
+ aProcessed.add(elem);
+
+ LOG.debug("Setting pk/version on " + aPersistent + " from " + aMerged);
+
+ if (aPersistent.getPrimaryKey() != null
+ && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
+ LOG.error("Mismatch between primary key values: " + aPersistent
+ + " " + aMerged);
+ } else {
+ aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
+ aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
+ }
+
+ Method[] methods = aPersistent.getClass().getMethods();
+ for (Method getter : methods) {
+ if (getter.getName().startsWith("get")) {
+ Class returnType = getter.getReturnType();
+
+ try {
+ if (Set.class.isAssignableFrom(returnType)) {
+ Set merged = (Set) getter.invoke(aMerged);
+ Set persistent = (Set) getter.invoke(aPersistent);
+ processSet(persistent, merged, aProcessed);
+ } else if (List.class.isAssignableFrom(returnType)) {
+ List merged = (List) getter.invoke(aMerged);
+ List persistent = (List) getter.invoke(aPersistent);
+ processList(persistent, merged, aProcessed);
+ } else if (Map.class.isAssignableFrom(returnType)) {
+ Map merged = (Map) getter.invoke(aMerged);
+ Map persistent = (Map) getter.invoke(aPersistent);
+ processMap(persistent, merged, aProcessed);
+ } else if (Persistent.class.isAssignableFrom(returnType)) {
+ Persistent merged = (Persistent) getter.invoke(aMerged);
+ Persistent persistent = (Persistent) getter
+ .invoke(aPersistent);
+ processPersistent(persistent, merged, aProcessed);
+ } else if (returnType.isArray()
+ && Persistent.class.isAssignableFrom(returnType
+ .getComponentType())) {
+ Persistent[] merged = (Persistent[]) getter
+ .invoke(aMerged);
+ Persistent[] persistent = (Persistent[]) getter
+ .invoke(aPersistent);
+ for (int i = 0; i < persistent.length; i++) {
+ processPersistent(persistent[i], merged[i],
+ aProcessed);
+ }
+ }
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Process the persistent objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processList(List aPersistent, List aMerged,
+ List<ObjectElem> aProcessed) {
+ Object[] merged = aMerged.toArray();
+ Object[] persistent = aPersistent.toArray();
+ if (merged.length != persistent.length) {
+ throw new RuntimeException("Array sizes differ " + merged.length
+ + " " + persistent.length);
+ }
+ for (int i = 0; i < merged.length; i++) {
+ assert merged[i].equals(persistent[i]);
+ if (merged[i] instanceof Persistent) {
+ processPersistent((Persistent) persistent[i],
+ (Persistent) merged[i], aProcessed);
+ }
+ }
+ }
+
+ /**
+ * Process the persistent objects in sets.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processSet(Set aPersistent, Set aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Array sizes differ " + aMerged.size()
+ + " " + aPersistent.size());
+ }
+ for (Object merged : aMerged) {
+ // Find the object that equals the merged[i]
+ for (Object persistent : aPersistent) {
+ if (persistent.equals(merged)) {
+ processPersistent((Persistent) persistent,
+ (Persistent) merged, aProcessed);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Process the Map objects in the collections.
+ *
+ * @param aPersistent
+ * Collection in the original object.
+ * @param aMerged
+ * Collection as a result of the merge.
+ * @param aProcessed
+ * List of processed persistent objects.
+ */
+ public static void processMap(Map aPersistent, Map aMerged,
+ List<ObjectElem> aProcessed) {
+ if (aMerged.size() != aPersistent.size()) {
+ throw new RuntimeException("Sizes differ " + aMerged.size() + " "
+ + aPersistent.size());
+ }
+ Set keys = aMerged.keySet();
+ for (Object key : keys) {
+ if (!aPersistent.containsKey(key)) {
+ throw new RuntimeException("Key '" + key + "' not found");
+ }
+ Object mergedValue = aMerged.get(key);
+ Object persistentValue = aPersistent.get(key);
+ if (mergedValue instanceof Persistent) {
+ if (persistentValue instanceof Persistent) {
+ processPersistent((Persistent) persistentValue,
+ (Persistent) mergedValue, aProcessed);
+ } else {
+ throw new RuntimeException(
+ "Value in original object is null, whereas merged object contains a value");
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+ wamblee.org
+</head>
+<body bgcolor="white">
+
+This package provides support utilities for persistence with hibernate.
+
+<!-- Put @see and @since tags down here. -->
+
+@since -
+
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.concurrency.spring;
+
+import org.springframework.aop.framework.ProxyFactoryBean;
+import org.wamblee.concurrency.AbstractLockTestCase;
+import org.wamblee.concurrency.JvmLock;
+import org.wamblee.concurrency.spring.LockAdvice;
+import org.wamblee.test.TimingUtils;
+
+/**
+ *
+ *
+ * @author Erik Brakkee
+ */
+public class LockAdviceTest extends AbstractLockTestCase {
+
+ private class Runner implements Runnable {
+ public void run() {
+ LockAdviceTest.this.getTracker().eventOccurred(ACQUIRED);
+ TimingUtils.sleep(SLEEP_TIME);
+ }
+ }
+
+ private Runnable _target;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Runner runner = new Runner();
+ LockAdvice advice = new LockAdvice(new JvmLock());
+
+ ProxyFactoryBean support = new ProxyFactoryBean();
+ support.setInterfaces(new Class[]{ Runnable.class });
+ support.setTarget(runner);
+ support.addAdvice(advice);
+ _target = (Runnable)support.getObject();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.concurrency.AbstractLockTestCase#runThread()
+ */
+ @Override
+ protected Thread runThread() {
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ try {
+ getTracker().eventOccurred(STARTED);
+ _target.run();
+ getTracker().eventOccurred(RELEASED);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ };
+ });
+ t.start();
+ return t;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general.spring;
+
+import org.wamblee.general.BeanFactoryException;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the spring bean factory.
+ *
+ * @author Erik Brakkee
+ */
+public class SpringBeanFactoryTest extends TestCase {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ }
+
+ public void testExistingBeanRefContext() {
+ SpringBeanFactory factory = new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "test");
+
+ String value1 = factory.find(String.class);
+ assertEquals("hello", value1);
+ String value2 = (String) factory.find("java.lang.String");
+ assertEquals("hello", value2);
+ String value3 = factory.find("java.lang.String", String.class);
+ assertEquals("hello", value3);
+
+ try {
+ factory.find("unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+
+ public void testUnknownBeanFactory() {
+ try {
+ new SpringBeanFactory(
+ "org/wamblee/general/beanRefContext.xml", "unknown");
+ } catch (BeanFactoryException e) {
+ return; // ok
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.general.spring;
+
+/**
+ * Test bean factory.
+ *
+ * @author Erik Brakkee
+ */
+public class TestBeanFactory extends SpringBeanFactory {
+
+
+ public TestBeanFactory() {
+ super("org/wamblee/general/beanRefContext.xml", "test");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test.spring;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+
+/**
+ * Exporting the hibernate mapping.
+ *
+ * @author Erik Brakkee
+ */
+public final class HibernateExporter {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateExporter() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(aArgs[1]);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaExport export = new SchemaExport(conf);
+ export.setDelimiter(";");
+ export.setOutputFile(file);
+ export.create(true, false);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test.spring;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.hibernate.cfg.Configuration;
+import org.hibernate.tool.hbm2ddl.SchemaUpdate;
+
+/**
+ * Exporting the hibernate mapping.
+ *
+ * @author Erik Brakkee
+ */
+public final class HibernateUpdater {
+
+ /**
+ * Disabled constructor.
+ *
+ */
+ private HibernateUpdater() {
+ // Empty
+ }
+
+ public static void main(String[] aArgs) throws IOException {
+ String file = aArgs[0];
+ File dir = new File(file);
+
+ Configuration conf = HibernateUtils.getConfiguration(dir);
+
+ SchemaUpdate lSchemaUpdate = new SchemaUpdate(conf);
+ lSchemaUpdate.execute(true, true);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test.spring;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.oro.io.AwkFilenameFilter;
+import org.hibernate.cfg.Configuration;
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.io.InputResource;
+
+/**
+ * Hibernate utilities.
+ *
+ * @author Erik Brakkee
+ */
+public final class HibernateUtils {
+
+ private static final String DATABASE_PROPS = "test.database.properties";
+
+ /**
+ * Disabled.
+ *
+ */
+ private HibernateUtils() {
+ // Empty
+ }
+
+ /**
+ * @param aDir
+ * @return
+ */
+ public static Configuration getConfiguration(File aDir) throws IOException {
+ Configuration conf = new Configuration();
+ File[] files = aDir.listFiles((FileFilter) (new AwkFilenameFilter(
+ ".*\\.hbm\\.xml")));
+ for (File f : files) {
+ System.out.println("Mapping file: " + f);
+ conf.addFile(f);
+ }
+
+ Map<String, String> dbProps = getHibernateProperties();
+
+ for (Map.Entry<String, String> entry : dbProps.entrySet()) {
+ System.out.println("Property: " + entry.getKey() + "="
+ + entry.getValue());
+ conf.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ return conf;
+ }
+
+ private static Map<String, String> getHibernateProperties()
+ throws IOException {
+
+ System.out.println("Reading properties file: " + DATABASE_PROPS);
+ InputResource lPropFile = new ClassPathResource(DATABASE_PROPS);
+ Properties props = new Properties();
+ props.load(lPropFile.getInputStream());
+
+ Map<String, String> result = new TreeMap<String, String>();
+ result.put("hibernate.connection.driver_class", props
+ .getProperty("database.driver"));
+ result.put("hibernate.connection.url", props
+ .getProperty("database.url"));
+ result.put("hibernate.connection.username", props
+ .getProperty("database.username"));
+ result.put("hibernate.connection.password", props
+ .getProperty("database.password"));
+
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.test.spring;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.wamblee.general.BeanFactory;
+import org.wamblee.general.BeanFactoryException;
+
+/**
+ * Bean factory which uses Spring.
+ *
+ * @author Erik Brakkee
+ */
+public class TestSpringBeanFactory implements BeanFactory {
+
+ private ApplicationContext _context;
+
+ public TestSpringBeanFactory(ApplicationContext aContext) {
+ _context = aContext;
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String)
+ */
+ public Object find(String aId) {
+ return find(aId, Object.class);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.Class)
+ */
+ public <T> T find(Class<T> aClass) {
+ return find(aClass.getName(), aClass);
+ }
+
+ /* (non-Javadoc)
+ * @see org.wamblee.general.BeanFactory#find(java.lang.String, java.lang.Class)
+ */
+ public <T> T find(String aId, Class<T> aClass) {
+ try {
+ Object obj = _context.getBean(aId, aClass);
+ assert obj != null;
+ return aClass.cast(obj);
+ } catch (BeansException e) {
+ throw new BeanFactoryException(e.getMessage(), e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.test.spring;
+
+import java.util.Map;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ *
+ * @author Erik Brakkee
+ */
+public interface TestTransactionCallback {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @return A map containg the resuls of the execution. This is a convenient
+ * method of returning multiple results from a call.
+ */
+ Map execute() throws Exception;
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.test.spring;
+
+/**
+ * Transaction callback for testing. The test will fail if any type of exception
+ * is thrown.
+ *
+ * @author Erik Brakkee
+ */
+public interface TestTransactionCallbackWithoutResult {
+ /**
+ * Executes code within a transaction, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ */
+ void execute() throws Exception;
+}
--- /dev/null
+SetterConfiguration:
+- add type based removal of setters.
+- add values(Class aType) method for selecting a setter.
+- add add(Class aType) method for selecting a type.
+- verify that superclass setters are also taken into account.
+
+
+
+opzoeken waar de provided interfaces van de scope gebruikt worden, als het
+goed is nog nergens. Scope moet een Component worden en publiceren van een
+interface leidt tot een nieuw provided interface.
+
+Component infrastructure
+* support simple component
+ - requires through constructor
+ - provides through getters
+* optional start and stop methods
+* giving names to a component must be optional
+* support for cached components and non-cached components.
+
+
+- Annotaties definieren
+
+
+@Component
+ class MyComponent {
+
+ public MyComponent(Def aArg, Ghi aArg2) {
+ }
+
+ @Provided(name = "name")
+ public Xyz getXyz();
+
+ @Start
+ public void myStart(); // Unlike the component
+}
+
+- Algemene classes maken die de constructor, start method, en
+ methodes identificeren om de component als Component te benaderen.
+ in een object, samen met een wrapper die deze info gebruikt om het
+ object als Component te exportern.
+ Er moet ook een wrapper zijn voor reeds geinstantieerde objecten.
+
+- wrapper maken die geannoteerde classes interpreteert en via de eerdere
+ wrapper het object ontsluiten als component.
+ Zowel geinstantieerde objecten als classes ondersteunen.
+
+- add support for private setters.
+- add support or exposing provided services
+ - superclass or interface of the concrete type instead of the concrete type
+ or not based on the concrete type at all.
+ - based on getters.
+- add support for field injection.
+
+- add support for annotation based configuration instead of API-based
+
+- add support for type conversions.
+
+- add support for multiplicity.
+
+- add support for aggregating multiple servicss.
+
+- add support for explicitly configuring which provided interface of
+ a container is provided by which internal service.
+
+- do not use equality but use logic for matching external required and
+ provided interfaces of a container.
+
+ e.g. a service T provided by a container can be provided by a service
+ U that provides subclasses of the provided interfaces of T
+
+ a service T required by a component inside a container is covered
+ by stronger requirements for subclasses of T.
+
+- separate logic for connecting required and provided interfaces
+ through a general graph model of the application.
+ Then use generic filters, validations, and graph traversal APIs
+ to implement the current functionality in the container.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system-general</artifactId>
+ <packaging>jar</packaging>
+ <name>/system/general</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <type>test-jar</type>
+ <version>0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * A Class Adapter adapts a given class to a Component.
+ *
+ * @author Erik Brakkee
+ */
+public class ClassAdapter extends AbstractComponent<Object> {
+
+ private ClassConfiguration _classConfig;
+
+ public ClassAdapter(String aName, ClassConfiguration aClassConfig) {
+ super(aName,
+ aClassConfig.getProvidedInterfaces().toArray(new ProvidedInterface[0]),
+ aClassConfig.getRequiredInterfaces().toArray(new RequiredInterface[0]));
+ _classConfig = aClassConfig;
+ }
+
+ @Override
+ protected Object doStart(Scope aScope) {
+
+ Object obj = _classConfig.create(aScope);
+ _classConfig.inject(aScope, obj);
+
+ for (ProvidedInterface provided: getProvidedInterfaces()) {
+ addInterface(provided, obj, aScope);
+ }
+
+ return obj;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ // Empty.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * The class configuration encapsulates the knowledge of how to wrap a class as a component.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class ClassConfiguration {
+
+ private Class _class;
+ private ConstructorConfiguration _constructorConfig;
+ private ObjectConfiguration _objectConfig;
+
+ /**
+ * Constructs the configuration. By default no constructor is selected and
+ * one of {@link #select(Class...)} or
+ * {@link #greedy()} must be called.
+ * @param aClass Class to construct.
+ */
+ public ClassConfiguration(Class aClass) {
+ _class = aClass;
+ _constructorConfig = new ConstructorConfiguration(aClass);
+ _objectConfig = new ObjectConfiguration(aClass);
+ }
+
+ public ConstructorConfiguration getConstructorConfig() {
+ return _constructorConfig;
+ }
+
+ public ObjectConfiguration getObjectConfig() {
+ return _objectConfig;
+ }
+
+ /**
+ * Creates the object in the given scope.
+ * @param aScope Scope containing required interfaces for this object.
+ * @return object.
+ */
+ public Object create(Scope aScope) {
+ return _constructorConfig.create(aScope);
+ }
+
+ /**
+ * Injects required interfaces through the setters
+ * @param aObject Object to inject into.
+ * @param aScope Scope in which injection takes place.
+ */
+ public void inject(Scope aScope, Object aObject) {
+ _objectConfig.inject(aScope, aObject);
+ }
+
+ public List<ProvidedInterface> getProvidedInterfaces() {
+ List<ProvidedInterface> result = new ArrayList<ProvidedInterface>();
+ result.add(new DefaultProvidedInterface("provided", _class));
+ return result;
+ }
+
+ public List<RequiredInterface> getRequiredInterfaces() {
+ List<RequiredInterface> result = new ArrayList<RequiredInterface>();
+ result.addAll(_constructorConfig.getRequiredInterfaces());
+ result.addAll(_objectConfig.getRequiredInterfaces());
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.wamblee.collections.CollectionFilter;
+import org.wamblee.conditions.Condition;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.core.SystemAssemblyException;
+
+/**
+ * Class that allows configuration of the constructor to use.
+ *
+ * In particular, it provides:
+ * <ul>
+ * <li> Selection of a constructor using explicit selection
+ * {@link #select(Class...)} or using the most greedy constructor
+ * {@link #greedy()}.
+ * </li>
+ * <li>
+ * Selection of methods to invoke to inject other objects into the object.
+ * </li>
+ * <li> Selection of fields to set.
+ * </li>
+ * </ul>
+ */
+public class ConstructorConfiguration {
+ private Class _class;
+ private Constructor<?> _constructor;
+ private ParameterValues _values;
+ private boolean _publicOnly;
+
+ /**
+ * Constructs the configuration. By default the public constructor with the
+ * most arguments will be used.
+ * @param aClass Class to construct.
+ */
+ public ConstructorConfiguration(Class aClass) {
+ _class = aClass;
+ _constructor = null;
+ _publicOnly = true;
+ }
+
+ /**
+ * Sets whether or no non public constructors are also considered.
+ * Reset the choice of a constructor to its default.
+ * @param aNonPublic
+ * @return
+ */
+ public ConstructorConfiguration setNonPublic(boolean aNonPublic) {
+ _publicOnly = !aNonPublic;
+ _constructor = null;
+ _values = null;
+ return this;
+ }
+
+ /**
+ * Selects an explicit constructor.
+ * @param aTypes Arguments of the constructor.
+ * @return Return the injector to allow call chaining.
+ */
+ public ConstructorConfiguration select(Class... aTypes) {
+ try {
+ _constructor = _class.getDeclaredConstructor(aTypes);
+ } catch (Exception e) {
+ throw new SystemAssemblyException(e.getMessage(), e);
+ }
+ resetValues();
+ return this;
+ }
+
+ /**
+ * Selects the greediest constructor.
+ * @return The injector to allow call chaining.
+ * @throws SystemAssemblyException if the greediest constructor cannot be uniquely
+ * identified.
+ */
+ public ConstructorConfiguration greedy() {
+ Constructor<?>[] declared = _class.getDeclaredConstructors();
+ if (declared.length == 0) {
+ throw new SystemAssemblyException("Class '" + _class
+ + " is an interface, primitive type, or array");
+ }
+ int max = -1;
+ List<Constructor<?>> checked = new ArrayList<Constructor<?>>();
+ CollectionFilter.filter(Arrays.asList(declared), checked,
+ new Condition<Constructor<?>>() {
+ @Override
+ public boolean matches(Constructor<?> aObject) {
+ if ( !_publicOnly ) {
+ return true;
+ } else {
+ return Modifier.isPublic(aObject.getModifiers());
+ }
+ }
+ });
+ for (Constructor ctor : checked) {
+ if (ctor.getParameterTypes().length > max) {
+ max = ctor.getParameterTypes().length;
+ }
+ }
+ final int max2 = max;
+ List<Constructor<?>> ctors = checked;
+ List<Constructor<?>> longest = new ArrayList<Constructor<?>>();
+ CollectionFilter.filter(ctors, longest,
+ new Condition<Constructor<?>>() {
+ @Override
+ public boolean matches(Constructor<?> aObject) {
+ return aObject.getParameterTypes().length == max2;
+ }
+ });
+ if (longest.size() > 1) {
+ throw new SystemAssemblyException(
+ "Greediest constructor cannot be uniquely determined");
+ }
+ _constructor = longest.get(0);
+ resetValues();
+ return this;
+ }
+
+ public ParameterValues getParameters() {
+ getConstructor(); // initialize constructor if needed.
+ return _values;
+ }
+
+ /**
+ * Resets the values.
+ */
+ private void resetValues() {
+ _constructor.setAccessible(true);
+ _values = new ParameterValues(_constructor.getParameterTypes());
+ }
+
+ /**
+ * Creates the object in the given scope.
+ * @param aScope Scope containing required interfaces for this object.
+ * @return object.
+ */
+ public Object create(Scope aScope) {
+ Object[] values = _values.values(aScope);
+ try {
+ return getConstructor().newInstance(values);
+ } catch (Exception e) {
+ throw new SystemAssemblyException("Could not construct object "
+ + getConstructor() + " " + Arrays.asList(values), e);
+ }
+ }
+
+ public List<RequiredInterface> getRequiredInterfaces() {
+ getConstructor(); // initialize constructor if needed.
+ return _values.getRequiredInterfaces();
+ }
+
+ private Constructor getConstructor() {
+ if (_constructor == null ) {
+ greedy();
+ }
+ return _constructor;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.container.Container;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+
+public class DefaultContainer extends Container {
+
+ public DefaultContainer(String aName) {
+ super(aName);
+ }
+
+ @Override
+ public DefaultContainer addComponent(Component aComponent) {
+ super.addComponent(aComponent);
+ return this;
+ }
+
+ public DefaultContainer addComponent(String aName, ClassConfiguration aConfiguration) {
+ return addComponent(new ClassAdapter(aName, aConfiguration));
+ }
+
+ public DefaultContainer addComponent(String aName, Object aObject, ObjectConfiguration aConfiguration) {
+ if ( !aConfiguration.appliesTo(aObject) ) {
+ throw new IllegalArgumentException("Configuration '" + aConfiguration + "' does nto applu to '" +
+ aObject + "'");
+ }
+ return addComponent(new ObjectAdapter(aName, aObject, aConfiguration));
+ }
+
+
+ @Override
+ public DefaultContainer addRequiredInterface(RequiredInterface aRequired) {
+ super.addRequiredInterface(aRequired);
+ return this;
+ }
+
+ @Override
+ public DefaultContainer addProvidedInterface(ProvidedInterface aProvided) {
+ super.addProvidedInterface(aProvided);
+ return this;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.Scope;
+
+/**
+ * Value provider that provides a fixed value.
+ *
+ * @author Erik Brakkee
+ */
+class FixedValueProvider implements ValueProvider {
+
+ private Object _value;
+
+ /**
+ * Constructs the value.
+ * @param aValue Value to construct.
+ */
+ public FixedValueProvider(Object aValue) {
+ _value = aValue;
+ }
+
+ @Override
+ public Object getValue(Scope aScope) {
+ return _value;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * An adapter class that adapts an existing object to a component.
+ *
+ * @author Erik Brakkee
+ */
+public class ObjectAdapter extends AbstractComponent<Object> {
+
+ private ObjectConfiguration _objectConfig;
+ private Object _object;
+
+ public ObjectAdapter(String aName, Object aObject, ObjectConfiguration aObjectConfig) {
+ super(aName,
+ new ProvidedInterface[] { new DefaultProvidedInterface(aName, aObject.getClass()) },
+ aObjectConfig.getRequiredInterfaces().toArray(new RequiredInterface[0]));
+ _objectConfig = aObjectConfig;
+ _object = aObject;
+ }
+
+ @Override
+ protected Object doStart(Scope aScope) {
+
+ _objectConfig.inject(aScope, _object);
+
+ for (ProvidedInterface provided: getProvidedInterfaces()) {
+ addInterface(provided, _object, aScope);
+ }
+
+ return _object;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ // Empty.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * General configuration for an instantiated object.
+ *
+ * @author Erik Brakkee
+ */
+public class ObjectConfiguration {
+
+ private Class _class;
+ private SetterConfiguration _setterConfig;
+
+ public ObjectConfiguration(Class aClass) {
+ _class = aClass;
+ _setterConfig = new SetterConfiguration(aClass);
+ }
+
+ /**
+ * Performs injection into an object of the configured class
+ * using information from the given scope.
+ * @param aScope Scope.
+ * @param aObject Object.
+ */
+ public void inject(Scope aScope, Object aObject) {
+ _setterConfig.inject(aScope, aObject);
+ }
+
+ public SetterConfiguration getSetterConfig() {
+ return _setterConfig;
+ }
+
+ public List<RequiredInterface> getRequiredInterfaces() {
+ List<RequiredInterface> result = new ArrayList<RequiredInterface>();
+ result.addAll(_setterConfig.getRequiredInterfaces());
+ return result;
+ }
+
+ public boolean appliesTo(Object aObject) {
+ return _class.isInstance(aObject);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * Represents parameter values for a method or constructor and allows for the configuration
+ * of how these values are retrieved.
+ *
+ * @author Erik Brakkee
+ */
+public class ParameterValues {
+ private String[] _names;
+ private Class[] _types;
+ private ValueProvider[] _values;
+
+ /**
+ * Constructs the configuration. By default no constructor is selected and
+ * one of {@link #select(Class...)} or
+ * {@link #greedy()} must be called.
+ * @param aClass Class to construct.
+ */
+ public ParameterValues(Class[] aTypes) {
+ _names = new String[aTypes.length];
+ for (int i = 0; i < aTypes.length; i++) {
+ _names[i] = "arg" + i;
+ }
+ _types = aTypes;
+ resetValues();
+ }
+
+ /**
+ * Constructs the configuration. By default no constructor is selected and
+ * one of {@link #select(Class...)} or
+ * {@link #greedy()} must be called.
+ * @param aNames Names of the arguments.
+ * @param aClass Class to construct.
+ */
+ public ParameterValues(String[] aNames, Class[] aTypes) {
+ assert aNames.length == aTypes.length;
+ _names = aNames;
+ _types = aTypes;
+ resetValues();
+ }
+
+ /**
+ * The types of the parameter values.
+ * @return Types.
+ */
+ public Class[] getTypes() {
+ return _types;
+ }
+
+ /**
+ * Sets argument i to be optional, meaning that null is allowed to be passed in.
+ * @param aArg Argument to set.
+ */
+ public ParameterValues setOptional(int aArg) {
+ _values[aArg] = new RequiredInterfaceProvider(new DefaultRequiredInterface(
+ "arg" + aArg, _types[aArg], true));
+ return this;
+ }
+
+ /**
+ * Sets the argument i to a fixed value.
+ * @param aArg Argument to set.
+ * @param aValue Value.
+ */
+ public ParameterValues setValue(int aArg, Object aValue) {
+ _values[aArg] = new FixedValueProvider(aValue);
+ return this;
+ }
+
+ /**
+ * Resets the values.
+ */
+ private void resetValues() {
+ _values = new ValueProvider[_types.length];
+ for (int i = 0; i < _values.length; i++) {
+ _values[i] = new RequiredInterfaceProvider(new DefaultRequiredInterface(
+ _names[i], _types[i]));
+ }
+ }
+
+ /**
+ * Gets the required interfaces to provide values that are not provided
+ * in another way.
+ * @return Required interfaces.
+ */
+ public List<RequiredInterface> getRequiredInterfaces() {
+ List<RequiredInterface> result = new ArrayList<RequiredInterface>();
+ for (ValueProvider provider: _values) {
+ if ( provider instanceof RequiredInterfaceProvider) {
+ result.add( ((RequiredInterfaceProvider)provider).getRequiredInterface());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the values to use in the given scope.
+ * @param aScope Scope within which to retrieve the values.
+ * @return Values.
+ */
+ public Object[] values(Scope aScope) {
+ Object[] values = new Object[_values.length];
+ for (int i = 0; i < _values.length; i++) {
+ values[i] = _values[i].getValue(aScope);
+ }
+ return values;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * Value provider that provides a value through a required interface.
+ *
+ * @author Erik Brakkee
+ */
+class RequiredInterfaceProvider implements ValueProvider {
+
+ private RequiredInterface _required;
+
+ /**
+ * Constructs the provider
+ * @param aRequired Required interface.
+ */
+ public RequiredInterfaceProvider(RequiredInterface aRequired) {
+ _required = aRequired;
+ }
+
+ @Override
+ public Object getValue(Scope aScope) {
+ return aScope.getInterfaceImplementation(_required.getProvider(), Object.class);
+ }
+
+ public RequiredInterface getRequiredInterface() {
+ return _required;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.awt.CompositeContext;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.wamblee.collections.CollectionFilter;
+import org.wamblee.conditions.Condition;
+import org.wamblee.conditions.FixedCondition;
+import org.wamblee.general.Pair;
+import org.wamblee.reflection.ReflectionUtils;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.core.SystemAssemblyException;
+
+/**
+ * Represents the configuration for exposing the setters of a class as required
+ * interfaces.
+ *
+ * @author Erik Brakkee
+ */
+public class SetterConfiguration {
+
+ private Class _class;
+ private boolean _publicOnly;
+
+ private Map<Method, ParameterValues> _setters;
+
+ /**
+ * Constructs the setter configuration. By default no setters are added.
+ *
+ * @param aClass
+ * Class which is being configured.
+ */
+ public SetterConfiguration(Class aClass) {
+ _class = aClass;
+ _publicOnly = true;
+ _setters = new HashMap<Method, ParameterValues>();
+ }
+
+ /**
+ * Makes sure that all available setters are used.
+ */
+ public SetterConfiguration initAllSetters() {
+ _setters.clear();
+ for (Method method: getAllSetters(_class, _publicOnly) ) {
+ _setters.put(method, createParameterValues(method));
+ }
+ return this;
+ }
+
+ /**
+ * Called to set whether non-public setters are also used. By default only
+ * public setters are used. The currently selected setters remain chosen.
+ *
+ * @param aIsNonPublic
+ * Non public flag.
+ */
+ public SetterConfiguration setNonPublic(boolean aIsNonPublic) {
+ _publicOnly = !aIsNonPublic;
+ return this;
+ }
+
+ /**
+ * Removes all setters.
+ *
+ * @return Reference to the current object to allow call chaining.
+ */
+ public SetterConfiguration clear() {
+ _setters.clear();
+ return this;
+ }
+
+ /**
+ * Removes a setter from the set of methods.
+ *
+ * @param aName
+ * Name of the setter to remove.
+ * @return Reference to the current object to allow call chaining.
+ */
+ public SetterConfiguration remove(String aName) {
+ for (Method method : _setters.keySet()) {
+ if (method.getName().equals(aName)) {
+ _setters.remove(method);
+ return this;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No method configured by the name of '" + aName + "'");
+ }
+
+ /**
+ * Removes the method from the set of methods.
+ * @param aMethod Method to remove.
+ * @return
+ */
+ public SetterConfiguration remove(Method aMethod) {
+ if ( !aMethod.getDeclaringClass().isAssignableFrom(_class) ) {
+ throw new RuntimeException("Method " + aMethod + " not found in class " + _class + " or its superclasses");
+ }
+ for (Method method : _setters.keySet()) {
+ if (method.equals(aMethod)) {
+ _setters.remove(method);
+ return this;
+ }
+ }
+ throw new IllegalArgumentException(
+ "Method '" + aMethod + "' was not configured. ");
+ }
+
+ /**
+ * Adds a given setter name to the setters.
+ *
+ * @param aName Name of a setter method.
+ * @return Reference to the current object to allow call chaining.
+ */
+ public SetterConfiguration add(final String aName) {
+ int oldlen = _setters.size();
+ List<Method> methods = new ArrayList<Method>();
+ CollectionFilter.filter(getAllSetters(_class, _publicOnly), methods,
+ new Condition<Method>() {
+ @Override
+ public boolean matches(Method aObject) {
+ return aObject.getName().equals(aName);
+ }
+
+ });
+ if (methods.size() == 0 ) {
+ throw new IllegalArgumentException("Method '" + aName
+ + "' not found in " + _class.getName());
+ }
+ // TODO is it possible to get more than one setter here in case the subclass overrides
+ // the baseclass method?
+ _setters.put(methods.get(0), createParameterValues(methods.get(0)));
+ return this;
+ }
+
+ /**
+ * Adds a given setter identified by the type it accepts to the list of
+ * setters.N
+ *
+ * @param aType
+ * Type to look for. Note that this must be the exact type as
+ * autoboxing and autounboxing is not used.
+ * @return Reference to the current object to allow call chaining.
+ * @throws IllegalArgumentException
+ * In case no setter is found or multiple setters are found.
+ */
+ public SetterConfiguration addSetter(final Class aType) {
+ List<Method> result = new ArrayList<Method>();
+ CollectionFilter.filter(getAllSetters(_class, _publicOnly), result,
+ new Condition<Method>() {
+ @Override
+ public boolean matches(Method aObject) {
+ Class type = aObject.getParameterTypes()[0];
+ return type.equals(aType);
+ }
+
+ });
+ if (result.size() == 0) {
+ throw new IllegalArgumentException("No setter found in class '"
+ + _class.getName()
+ + "' that has a setter with argument type '"
+ + aType.getName() + "'");
+ }
+ if (result.size() > 1) {
+ String setters = "";
+ for (Method method : result) {
+ setters += method.getName() + " ";
+ }
+ throw new IllegalArgumentException(
+ "Multiple setters found in class '" + _class.getName()
+ + " that accept type '" + aType.getName() + "': "
+ + setters);
+ }
+ Method method = result.get(0);
+ _setters.put(method, createParameterValues(method));
+ return this;
+ }
+
+ /**
+ * Gets all setters for the current class.
+ *
+ * @return List of all setters.
+ */
+ public static List<Method> getAllSetters(Class aClass,
+ boolean aPublicOnly) {
+ List<Method> result = new ArrayList<Method>();
+ for (Method method : getAllMethods(aClass)) {
+ if (!aPublicOnly || Modifier.isPublic(method.getModifiers())) {
+ if (method.getName().startsWith("set")
+ && method.getParameterTypes().length == 1) {
+ method.setAccessible(true);
+ result.add(method);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static ParameterValues createParameterValues(Method aMethod) {
+
+ Class[] paramTypes = aMethod.getParameterTypes();
+ String[] paramNames = new String[paramTypes.length];
+ for (int i = 0; i < paramTypes.length; i++) {
+ paramNames[i] = aMethod.getName() + "." + i;
+ }
+ return new ParameterValues(paramNames, paramTypes);
+ }
+
+ private static final List<Method> getAllMethods(Class aClass) {
+ return ReflectionUtils.getAllMethods(aClass);
+ }
+
+ /**
+ * Gets the required interfaces based on the configured setteres.
+ *
+ * @return List of required interfaces.
+ */
+ public List<RequiredInterface> getRequiredInterfaces() {
+ List<RequiredInterface> result = new ArrayList<RequiredInterface>();
+ for (Method method : _setters.keySet()) {
+ result.addAll(_setters.get(method).getRequiredInterfaces());
+ }
+ return result;
+ }
+
+ /**
+ * Invokes all configured setters with the appropriate values.
+ *
+ * @param aScope
+ * Scope within which invocation takes place.
+ * @param aObject
+ * Object on which the invocation takes place.
+ */
+ public void inject(Scope aScope, Object aObject) {
+ if (!_class.isInstance(aObject)) {
+ throw new IllegalArgumentException("Object '" + aObject
+ + "' is not an instance of " + _class.getName());
+ }
+ for (Method method : _setters.keySet()) {
+ ParameterValues values = _setters.get(method);
+
+ try {
+ method.invoke(aObject, values.values(aScope));
+ } catch (IllegalAccessException e) {
+ throw new SystemAssemblyException("Problem invoking " + method
+ + " with " + values, e);
+ } catch (InvocationTargetException e) {
+ throw new SystemAssemblyException("Problem invoking " + method
+ + " with " + values, e);
+ }
+ }
+ }
+
+ /**
+ * Returns the parameter values for allowing detailed configuration of how
+ * parameter values are set.
+ *
+ * @param aSetter
+ * Setter name without the "set" prefix with the first character
+ * converted to lower case.
+ * @return Parameter values.
+ */
+ public ParameterValues values(String aMethod) {
+ for (Method method : _setters.keySet()) {
+ if (method.getName().equals(aMethod)) {
+ return _setters.get(method);
+ }
+ }
+ throw new IllegalArgumentException("No setter method '" + aMethod
+ + "' found");
+ }
+
+ public List<Method> getSetters() {
+ return new ArrayList<Method>(_setters.keySet());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.Scope;
+
+/**
+ * Interface used to provide values for arguments of methods and constructors.
+ *
+ * @author Erik Brakkee
+ */
+interface ValueProvider {
+
+ Object getValue(Scope aScope);
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.components;
+
+public class ORMappingConfig {
+
+ public enum DatabaseType {
+ MYSQL_INNO_DB, DERBY;
+
+ public static interface Switch<T> {
+ T handleMySqlInnoDb();
+
+ T handleDerby();
+ }
+
+ public <T> T handleCases(Switch<T> aSwitch) {
+
+ switch (this) {
+ case MYSQL_INNO_DB: {
+ return aSwitch.handleMySqlInnoDb();
+ }
+ case DERBY: {
+ return aSwitch.handleDerby();
+ }
+ default: {
+ throw new RuntimeException("Unhandled case " + this);
+ }
+ }
+ }
+ };
+
+ private boolean _schemaUpdate;
+ private DatabaseType _type;
+
+ public ORMappingConfig(boolean aSchemaUpdate, DatabaseType aType) {
+ _schemaUpdate = aSchemaUpdate;
+ _type = aType;
+ }
+
+ public boolean isSchemaUpdate() {
+ return _schemaUpdate;
+ }
+
+ public DatabaseType getType() {
+ return _type;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.components;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import org.wamblee.io.InputResource;
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.Scope;
+
+/**
+ * Property component that reads a property file and provides
+ * it to other components through a provided interface.
+ *
+ * NOTE: when multiple property components are used, disambiguation
+ * of provided and required interfaces is needed.
+ *
+ * @author Erik Brakkee
+ */
+public class PropertyComponent extends AbstractComponent<Properties> {
+
+ private static ProvidedInterface PROPS = new DefaultProvidedInterface("props", Properties.class);
+
+ private Properties _props;
+
+ public PropertyComponent(String aName, InputResource aResource) throws IOException {
+ this(aName, readProps(aResource));
+ }
+
+ private static Properties readProps(InputResource aResource) throws IOException {
+ Properties props = new Properties();
+ props.load(aResource.getInputStream());
+ return props;
+ }
+
+ public PropertyComponent(String aName, Properties aProps) {
+ super(aName);
+ _props = aProps;
+
+ addProvidedInterface(PROPS);
+ }
+
+ @Override
+ protected Properties doStart(Scope aScope) {
+ addInterface(PROPS, _props, aScope);
+ return _props;
+ }
+
+ @Override
+ protected void doStop(Properties aRuntime) {
+ // Empty
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.container;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.general.Pair;
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultScope;
+import org.wamblee.system.core.NamedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.system.graph.CompositeEdgeFilter;
+import org.wamblee.system.graph.component.ComponentGraph;
+import org.wamblee.system.graph.component.ConnectExternalProvidedProvidedFilter;
+import org.wamblee.system.graph.component.ConnectRequiredExternallyRequiredEdgeFilter;
+import org.wamblee.system.graph.component.ConnectRequiredProvidedEdgeFilter;
+
+/**
+ * Container consisting of multiple components.
+ *
+ * @author Erik Brakkee
+ */
+public class Container extends AbstractComponent<Scope> {
+
+ private static final Log LOG = LogFactory.getLog(Container.class);
+
+ private List<Component> _components;
+ private CompositeEdgeFilter _edgeFilter;
+ private boolean _sealed;
+
+ /**
+ * Constructs the container
+ *
+ * @param aName
+ * Name of the container
+ * @param aComponents
+ * Components.
+ * @param aProvided
+ * Provided services of the container
+ * @param aRequired
+ * Required services by the container.
+ */
+ public Container(String aName, Component[] aComponents,
+ List<ProvidedInterface> aProvided, List<RequiredInterface> aRequired) {
+ super(aName, aProvided, aRequired);
+ _components = new ArrayList<Component>();
+
+ _edgeFilter = new CompositeEdgeFilter();
+ _sealed = false;
+ for (Component component : aComponents) {
+ addComponent(component);
+ }
+ }
+
+ /**
+ * Constructs the container
+ *
+ * @param aName
+ * Name of the container
+ * @param aComponents
+ * Components.
+ * @param aProvided
+ * Provided services of the container
+ * @param aRequired
+ * Required services by the container.
+ */
+ public Container(String aName, Component[] aComponents,
+ ProvidedInterface[] aProvided, RequiredInterface[] aRequired) {
+ this(aName, aComponents, Arrays.asList(aProvided), Arrays.asList(aRequired));
+ }
+
+ public Container(String aName) {
+ this(aName, new Component[0], new ProvidedInterface[0],
+ new RequiredInterface[0]);
+ }
+
+ public Container addComponent(Component aComponent) {
+ checkSealed();
+ if (aComponent.getContext() != null) {
+ throw new SystemAssemblyException(
+ "Inconsistent hierarchy, component '"
+ + aComponent.getName()
+ + "' is already part of another hierarchy");
+ }
+ if ( findComponent(aComponent.getName()) != null ) {
+ throw new SystemAssemblyException("Duplicate component '"
+ + aComponent.getName() + "'");
+ }
+ _components.add(aComponent);
+ aComponent.addContext(getQualifiedName());
+ return this;
+ }
+
+ /**
+ * Explictly connects required and provided interfaces.
+ * @param aClientComponent Client component, may not be null.
+ * @param aRequiredInterface Required interface. If null it means all required interfaces.
+ * @param aServerComponent Server component to connect to. If null, it means that no server components
+ * may be connected to and the provider of the required interface will be null.
+ * @param aProvidedInterface Provided interface. If null, it means that there is no restriction on the
+ * name of the provided interface and that it is automatically selected.
+ */
+ public void connectRequiredProvided(String aClientComponent, String aRequiredInterface,
+ String aServerComponent, String aProvidedInterface) {
+ checkSealed();
+ Component client = findComponent(aClientComponent);
+ Component server = findComponent(aServerComponent);
+ if ( client == null ) {
+ throw new SystemAssemblyException(getQualifiedName() + ": No component '" + aClientComponent + "' in the container");
+ }
+ if ( aRequiredInterface != null ) {
+ if ( findInterface(client.getRequiredInterfaces(), aRequiredInterface) == null ) {
+ throw new SystemAssemblyException(
+ getQualifiedName() + ": Component '" + aClientComponent + "' does not have a required interface named '"
+ + aRequiredInterface + "'");
+ }
+ }
+ if ( server == null ) {
+ throw new SystemAssemblyException("No component '" + aClientComponent + "' in the container");
+ }
+ if ( aProvidedInterface != null ) {
+ if ( findInterface(server.getProvidedInterfaces(), aProvidedInterface) == null) {
+ throw new SystemAssemblyException(
+ getQualifiedName() + ": Component '" + aServerComponent + "' does not have a provided interface named '"
+ + aProvidedInterface + "'");
+ }
+ }
+ _edgeFilter.add(new ConnectRequiredProvidedEdgeFilter(aClientComponent, aRequiredInterface, aServerComponent, aProvidedInterface));
+ }
+
+ /**
+ * Explicitly connects a externally required interface to an internally required interface.
+ * @param aComponent Component requiring the interface (must be non-null).
+ * @param aRequiredInterface Required interface of the component (must be non-null).
+ * @param aExternalRequiredInterface Externally required interface (must be non-null).
+ */
+ public void connectExternalRequired(String aComponent, String aRequiredInterface,
+ String aExternalRequiredInterface) {
+ checkSealed();
+ Component client = findComponent(aComponent);
+ if ( client == null ) {
+ throw new SystemAssemblyException(getQualifiedName() + ": No component '" + aComponent + "' in the container");
+ }
+ if ( aRequiredInterface != null ) {
+ if ( findInterface(client.getRequiredInterfaces(), aRequiredInterface) == null ) {
+ throw new SystemAssemblyException(
+ getQualifiedName() + ": Component '" + aComponent + "' does not have a required interface named '"
+ + aRequiredInterface + "'");
+ }
+ }
+ if ( aExternalRequiredInterface != null) {
+ if ( findInterface(getRequiredInterfaces(), aExternalRequiredInterface) == null ) {
+ throw new SystemAssemblyException(
+ getQualifiedName() + ": container does not have a required interface named '"
+ + aExternalRequiredInterface + "'");
+ }
+ }
+ _edgeFilter.add(new ConnectRequiredExternallyRequiredEdgeFilter(
+ aComponent, aRequiredInterface, aExternalRequiredInterface));
+ }
+
+ public void connectExternalProvided(String aExternalProvided, String aComponent, String aProvidedInterface) {
+ checkSealed();
+ Component server = findComponent(aComponent);
+
+
+ if ( server == null ) {
+ throw new SystemAssemblyException("No component '" + aComponent + "' in the container");
+ }
+ if ( aProvidedInterface != null ) {
+ if ( findInterface(server.getProvidedInterfaces(), aProvidedInterface) == null) {
+ throw new SystemAssemblyException(
+ getQualifiedName() + ": Component '" + aComponent + "' does not have a provided interface named '"
+ + aProvidedInterface + "'");
+ }
+ }
+ if ( aExternalProvided != null ) {
+ if ( findInterface(getProvidedInterfaces(), aExternalProvided) == null) {
+ throw new SystemAssemblyException(
+ getQualifiedName() + ": Container does not have a provided interface named '"
+ + aExternalProvided + "'");
+ }
+ }
+ _edgeFilter.add(new ConnectExternalProvidedProvidedFilter(aExternalProvided, aComponent, aProvidedInterface));
+ }
+
+
+ @Override
+ public Container addProvidedInterface(ProvidedInterface aProvided) {
+ checkSealed();
+ super.addProvidedInterface(aProvided);
+ return this;
+ }
+
+ @Override
+ public Container addRequiredInterface(RequiredInterface aRequired) {
+ checkSealed();
+ super.addRequiredInterface(aRequired);
+ return this;
+ }
+
+ @Override
+ public void addContext(String aContext) {
+ super.addContext(aContext);
+ for (Component component : _components) {
+ component.addContext(aContext);
+ }
+ }
+
+ /**
+ * Validates the components together to check that there are no required
+ * services not in the required list and no services in the provided list
+ * that cannot be provided. Also logs a warning in case of superfluous
+ * requirements.
+ *
+ * @throws SystemAssemblyException
+ * in case of any validation problems.
+ */
+ public void validate() {
+ doStartOptionalDryRun(null, true);
+ }
+
+ /**
+ * Seal the container, meaning that no further components or interfaces may
+ * be added.
+ */
+ public void seal() {
+ _sealed = true;
+ }
+
+ /**
+ * Checks if the container is sealed.
+ *
+ * @return True iff the container is sealed.
+ */
+ public boolean isSealed() {
+ return _sealed;
+ }
+
+ /**
+ * Utility method to start with an empty external scope. This is useful for
+ * top-level containers which are not part of another container.
+ *
+ * @return Scope.
+ */
+ public Scope start() {
+ Scope scope = new DefaultScope(getProvidedInterfaces());
+ return super.start(scope);
+ }
+
+ @Override
+ protected Scope doStart(Scope aExternalScope) {
+ validate();
+ Scope scope = new DefaultScope(getProvidedInterfaces().toArray(new ProvidedInterface[0]), aExternalScope);
+ ComponentGraph graph = doStartOptionalDryRun(scope, false);
+ exposeProvidedInterfaces(graph, aExternalScope, scope);
+ seal();
+ return scope;
+ }
+
+ private void exposeProvidedInterfaces(ComponentGraph aGraph, Scope aExternalScope,
+ Scope aInternalScope) {
+ for (Pair<ProvidedInterface,ProvidedInterface> mapping:
+ aGraph.findExternalProvidedInterfaceMapping()) {
+ Object svc = aInternalScope.getInterfaceImplementation(mapping.getSecond(), Object.class);
+ addInterface(mapping.getFirst(), svc, aExternalScope);
+ }
+ }
+
+ private ComponentGraph doStartOptionalDryRun(Scope aScope, boolean aDryRun) {
+ ComponentGraph graph = createComponentGraph();
+ graph.validate();
+ graph.link();
+
+ LOG.info("Starting '" + getQualifiedName() + "'");
+
+ List<Component> started = new ArrayList<Component>();
+ for (Component component : _components) {
+ try {
+ // Start the service.
+ if (!aDryRun) {
+ Object runtime = component.start(aScope);
+ aScope.addRuntime(component, runtime);
+ started.add(component);
+ }
+ } catch (SystemAssemblyException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ LOG.error(getQualifiedName() + ": could not start '"
+ + component.getQualifiedName() + "'", e);
+ stopAlreadyStartedComponents(started, aScope);
+ throw e;
+ }
+ }
+ return graph;
+ }
+
+ private ComponentGraph createComponentGraph() {
+ ComponentGraph graph = new ComponentGraph();
+ for (RequiredInterface req : getRequiredInterfaces()) {
+ graph.addRequiredInterface(this, req);
+ }
+ for (Component comp : _components) {
+ graph.addComponent(comp);
+ }
+ for (ProvidedInterface prov: getProvidedInterfaces()) {
+ graph.addProvidedInterface(this, prov);
+ }
+
+ graph.addEdgeFilter(_edgeFilter);
+ return graph;
+ }
+
+ private void stopAlreadyStartedComponents(List<Component> aStarted,
+ Scope aScope) {
+ // an exception occurred, stop the successfully started
+ // components
+ for (int i = aStarted.size() - 1; i >= 0; i--) {
+ try {
+ Component component = aStarted.get(i);
+ aStarted.get(i).stop(aScope.getRuntime(component));
+ } catch (Throwable t) {
+ LOG.error(getQualifiedName() + ": error stopping "
+ + aStarted.get(i).getQualifiedName());
+ }
+ }
+ }
+
+ @Override
+ protected void doStop(Scope aScope) {
+ for (int i = _components.size() - 1; i >= 0; i--) {
+ Component component = _components.get(i);
+ Object runtime = aScope.getRuntime(component);
+ component.stop(runtime);
+ }
+ }
+
+ private void checkSealed() {
+ if (_sealed) {
+ throw new SystemAssemblyException("Container is sealed");
+ }
+ }
+
+ /**
+ * Finds a component based on the non-qualified name of the component.
+ * @param aName Component name.
+ * @return Component or null if not found.
+ */
+ public Component findComponent(String aName) {
+ for (Component<?> component: _components) {
+ if ( component.getName().equals(aName)) {
+ return component;
+ }
+ }
+ return null;
+ }
+
+ private static <T extends NamedInterface> T findInterface(List<T> aInterfaces, String aInterfaceName) {
+ for (T intf: aInterfaces) {
+ if ( intf.getName().equals(aInterfaceName)) {
+ return intf;
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Abstract subsystem class making it easy to implement new subsystems.
+ */
+public abstract class AbstractComponent<Type> implements Component<Type> {
+
+ private static final Log LOG = LogFactory.getLog(AbstractComponent.class);
+
+ private ThreadLocal<List<ProvidedInterface>> _remaining;
+
+ private String _context;
+ private String _name;
+ private List<ProvidedInterface> _provided;
+ private List<RequiredInterface> _required;
+
+ /**
+ * Constructs the subsystem.
+ *
+ * @param aName
+ * Name of the system.
+ * @param aProvided
+ * Provided services.
+ * @param aRequired
+ * Required services.
+ */
+ protected AbstractComponent(String aName, List<ProvidedInterface> aProvided,
+ List<RequiredInterface> aRequired) {
+ _remaining = new ThreadLocal<List<ProvidedInterface>>();
+ _context = null;
+ _name = aName;
+ _provided = new ArrayList<ProvidedInterface>(aProvided);
+ _required = new ArrayList<RequiredInterface>(aRequired);
+ }
+
+ /**
+ * Constructs the subsystem.
+ *
+ * @param aName
+ * Name of the system.
+ * @param aProvided
+ * Provided services.
+ * @param aRequired
+ * Required services.
+ */
+ protected AbstractComponent(String aName, ProvidedInterface[] aProvided,
+ RequiredInterface[] aRequired) {
+ this(aName, Arrays.asList(aProvided), Arrays.asList(aRequired));
+ }
+
+ protected AbstractComponent(String aName) {
+ this(aName, new ProvidedInterface[0], new RequiredInterface[0]);
+ }
+
+ public AbstractComponent<Type> addProvidedInterface(ProvidedInterface aProvided) {
+ _provided.add(aProvided);
+ return this;
+ }
+
+ public AbstractComponent<Type> addRequiredInterface(RequiredInterface aRequired) {
+ _required.add(aRequired);
+ return this;
+ }
+
+ @Override
+ public final String getName() {
+ return _name;
+ }
+
+ @Override
+ public void addContext(String aContext) {
+ if (_context == null) {
+ _context = aContext;
+ } else {
+ _context = aContext + "." + _context;
+ }
+ }
+
+ @Override
+ public String getContext() {
+ return _context;
+ }
+
+ @Override
+ public String getQualifiedName() {
+ if (_context == null) {
+ return getName();
+ }
+ return _context + "." + getName();
+ }
+
+ @Override
+ public final List<ProvidedInterface> getProvidedInterfaces() {
+ return Collections.unmodifiableList(_provided);
+ }
+
+ @Override
+ public final List<RequiredInterface> getRequiredInterfaces() {
+ return Collections.unmodifiableList(_required);
+ }
+
+ @Override
+ public final Type start(Scope aScope) {
+ LOG.info("Initialization starting '" + getQualifiedName() + "'");
+ List<ProvidedInterface> oldRemaining = _remaining.get();
+ _remaining.set(new ArrayList<ProvidedInterface>(getProvidedInterfaces()));
+ try {
+ Type runtime = doStart(aScope);
+ checkNotStartedInterfaces();
+ LOG.info("Initialization finished '" + getQualifiedName() + "'");
+ return runtime;
+ } finally {
+ _remaining.set(oldRemaining);
+ }
+ }
+
+ private void checkNotStartedInterfaces() {
+ if (_remaining.get().size() > 0) {
+ String notProvided = "";
+ for (ProvidedInterface provided : _remaining.get()) {
+ notProvided += "\nComponent " + getQualifiedName()
+ + " did not start interface " + provided;
+ }
+ throw new SystemAssemblyException(notProvided);
+ }
+ }
+
+ /**
+ * Must be implemented for initializing the subsystem. The implementation
+ * must call {@link #addInterface(ProvidedInterface, Object, Scope)} for each service that is started.
+ *
+ * @return Returns the runtime of the component.
+ */
+ protected abstract Type doStart(Scope aScope);
+
+ /**
+ * Implementations must call this method to indicate that a new service has
+ * been started.
+ *
+ * @param aDescriptor
+ * Provided interface.
+ * @param aService
+ * Implementation of the interface.
+ * @param aScope
+ * scope in which to publish the implementation.
+ */
+ protected final void addInterface(ProvidedInterface aDescriptor,
+ Object aService, Scope aScope) {
+ LOG.info("Interface '" + getQualifiedName() + "."
+ + aDescriptor.getName() + "' started.");
+ if ( !_remaining.get().remove(aDescriptor) ) {
+ throw new SystemAssemblyException("Component '" + getQualifiedName() + "' started an unexpected interface '" +
+ aDescriptor + "' that was not registerd as a provided interface before");
+ }
+ aScope.publishInterface(aDescriptor, aService);
+ }
+
+ @Override
+ public void stop(Type aRuntime) {
+ LOG.info("Stopping initiated '" + getQualifiedName() + "'");
+ doStop(aRuntime);
+ LOG.info("Stopping completed '" + getQualifiedName() + "'");
+ }
+
+ protected abstract void doStop(Type aRuntime);
+
+ @Override
+ public String toString() {
+ return getQualifiedName();
+ }
+
+ public ProvidedInterface findProvidedInterface(String aName) {
+ for (ProvidedInterface provided: getProvidedInterfaces()) {
+ if ( provided.getName().equals(aName)) {
+ return provided;
+ }
+ }
+ return null;
+ }
+
+ public RequiredInterface findRequiredInterface(String aName) {
+ for (RequiredInterface required: getRequiredInterfaces()) {
+ if ( required.getName().equals(aName)) {
+ return required;
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.List;
+
+/**
+ * A component represents a part of a system that requires a
+ * number of interfaces and provides a number of interfaces.
+ *
+ * The component interface provides the meta-data for a component.
+ * After calling {@link #start(Scope)}, an actual runtime representation of the
+ * component can be created which is independent of this component.
+ * As a special case, the runtime representation may be identical to the
+ * component instance but in general it is not. This allows a component to be
+ * used as a factory for creating objects.
+ *
+ *
+ * @author Erik Brakkee
+ */
+public interface Component<Type> {
+
+ /**
+ * Gets the name of the subsystem.
+ * @return Subsystem name.
+ */
+ String getName();
+
+ /**
+ * Prepends the context with a super context.
+ */
+ void addContext(String aContext);
+
+ /**
+ * Getst the context.
+ * @return Context or null if not set.
+ */
+ String getContext();
+
+ /**
+ * Gets the fully qualified name of the component which includes
+ * the context of the component.
+ * This method can only be used after the component has started.
+ * @return Qualified name.
+ */
+ String getQualifiedName();
+
+ /**
+ * Gets a description of the provided interfaces.
+ * @return Provided interfaces.
+ */
+ List<ProvidedInterface> getProvidedInterfaces();
+
+ /**
+ * Gets a description of the required interfaces.
+ * @return Required interfaces.
+ */
+ List<RequiredInterface> getRequiredInterfaces();
+
+
+ /**
+ * Initialises the subsystem by starting all the services that
+ * it described as provided.
+ * @param aScope Scope with external interface implementations that are available. The component
+ * must publish its runtime and its provided interfaces in this scope.
+ * @return Gets an object representing the runtime of the component.
+ */
+ Type start(Scope aScope);
+
+ /**
+ * Stops a component.
+ * @param aRuntime THe runtime part of the component.
+ */
+ void stop(Type aRuntime);
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * Default implementation of a service descriptor.
+ *
+ * @author Erik Brakkee
+ */
+public class DefaultProvidedInterface implements ProvidedInterface {
+
+ private String _name;
+ private Class[] _interfaces;
+
+ /**
+ * Constructs the descriptor.
+ * @param aInterface Type of service.
+ */
+ public DefaultProvidedInterface(String aName, Class aInterface) {
+ this(aName, new Class[] { aInterface });
+ }
+
+ public DefaultProvidedInterface(String aName, Class[] aInterfaces) {
+ _name = aName;
+ _interfaces = Arrays.copyOf(aInterfaces, aInterfaces.length);
+ }
+
+ @Override
+ public String getName() {
+ return _name;
+ }
+
+ @Override
+ public Class[] getInterfaceTypes() {
+ return _interfaces;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append(getName());
+ buf.append(":");
+ for (Class intf: _interfaces) {
+ buf.append(" " + intf.getName());
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public boolean equals(Object aObj) {
+ return this == aObj;
+ /*
+ if ( !(aObj instanceof DefaultProvidedInterface)) {
+ return false;
+ }
+ DefaultProvidedInterface provided = (DefaultProvidedInterface)aObj;
+ return getEqualsRepresentation().equals(provided.getEqualsRepresentation());
+ */
+ }
+
+ @Override
+ public int hashCode() {
+ return getEqualsRepresentation().hashCode();
+ }
+
+ @Override
+ public boolean covers(ProvidedInterface aInterface) {
+ // TODO do more than just equals.
+ if ( !(aInterface instanceof DefaultProvidedInterface)) {
+ return false;
+ }
+ return getEqualsRepresentation().equals(((DefaultProvidedInterface)aInterface).getEqualsRepresentation());
+ }
+
+
+ private String getEqualsRepresentation() {
+ List<String> result = new ArrayList<String>();
+ for (Class cls: _interfaces) {
+ result.add(cls.getName());
+ }
+ Collections.sort(result);
+ String value = "";
+ for (String str: result) {
+ value += ":" + str;
+ }
+ return value;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.Arrays;
+
+import org.wamblee.reflection.ReflectionUtils;
+
+public class DefaultRequiredInterface implements RequiredInterface {
+
+ private String _name;
+ private boolean _optional;
+ private Class[] _required;
+ private ProvidedInterface _provider;
+
+ public DefaultRequiredInterface(String aName, Class aInterface) {
+ this(aName, new Class[] { aInterface });
+ }
+
+ public DefaultRequiredInterface(String aName, Class[] aInterfaces) {
+ this(aName, aInterfaces, false);
+ }
+
+ public DefaultRequiredInterface(String aName, Class aInterface, boolean aIsOptional) {
+ this(aName, new Class[] { aInterface }, aIsOptional );
+ }
+
+
+ public DefaultRequiredInterface(String aName, Class[] aInterfaces, boolean aIsOptional) {
+ _name = aName;
+ _optional = aIsOptional;
+ _required = aInterfaces;
+ }
+
+ @Override
+ public String getName() {
+ return _name;
+ }
+
+ @Override
+ public boolean isOptional() {
+ return _optional;
+ }
+
+ @Override
+ public boolean implementedBy(ProvidedInterface aDescriptor) {
+ Class[] provided = aDescriptor.getInterfaceTypes();
+ for (Class required : _required) {
+ if ( !serviceProvided(required, provided)) {
+ return false;
+ }
+ }
+ // all required interfaces are provided.
+ return true;
+ }
+
+ /**
+ * Check if the required interface is implemented by one of the provided interfaces.
+ * @param aRequired required interface
+ * @param aProvided Provided interfaces.
+ * @return
+ */
+ private boolean serviceProvided(Class aRequired, Class[] aProvided) {
+ for (Class provided: aProvided) {
+ try {
+ provided = ReflectionUtils.wrapIfNeeded(provided);
+ aRequired = ReflectionUtils.wrapIfNeeded(aRequired);
+ provided.asSubclass(aRequired);
+ return true;
+ } catch (ClassCastException e) {
+ // No match, try the next one.
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public ProvidedInterface getProvider() {
+ return _provider;
+ }
+
+ @Override
+ public void setProvider(ProvidedInterface aProvider) {
+ assert aProvider != null;
+ assert implementedBy(aProvider);
+ _provider = aProvider;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public boolean covers(RequiredInterface obj) {
+ // TODO do more than equals.
+ if ( !(obj instanceof DefaultRequiredInterface)) {
+ return false;
+ }
+ DefaultRequiredInterface descr = (DefaultRequiredInterface)obj;
+ if ( _required.length != descr._required.length ) {
+ return false;
+ }
+ String[] interfaces1 = new String[_required.length];
+ String[] interfaces2 = new String[_required.length];
+ for (int i = 0; i < _required.length; i++) {
+ interfaces1[i] = _required[i].getName();
+ interfaces2[i] = descr._required[i].getName();
+ }
+ Arrays.sort(interfaces1);
+ Arrays.sort(interfaces2);
+ return Arrays.equals(interfaces1, interfaces2);
+ }
+
+ @Override
+ public int hashCode() {
+ return _required.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("." + getName() + ":");
+ for (Class intf: _required) {
+ buf.append("." + intf.getName());
+ }
+ return buf.toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DefaultScope implements Scope {
+
+ private List<Scope> _parents;
+ private Map<String, Object> _properties;
+ private Map<String, Object> _runtimes;
+ private Map<ProvidedInterface, ProvidedInterfaceImplementation> _provided;
+ private List<ProvidedInterface> _externallyProvided;
+
+ public DefaultScope(List<ProvidedInterface>aExternallyProvided) {
+ this(aExternallyProvided.toArray(new ProvidedInterface[0]));
+ }
+
+ public DefaultScope(ProvidedInterface[] aExternallyProvided) {
+ this(aExternallyProvided, new ArrayList<Scope>());
+ }
+
+ public DefaultScope(ProvidedInterface[] aExternallyProvided, Scope aParent) {
+ this(aExternallyProvided, Arrays.asList(new Scope[] { aParent }));
+ }
+
+ public DefaultScope(ProvidedInterface[] aExternallyProvided,
+ List<Scope> aParent) {
+ _parents = new ArrayList<Scope>(aParent);
+ _properties = new HashMap<String, Object>();
+ _runtimes = new HashMap<String, Object>();
+ _provided = new HashMap<ProvidedInterface, ProvidedInterfaceImplementation>();
+ _externallyProvided = new ArrayList<ProvidedInterface>();
+ _externallyProvided.addAll(Arrays.asList(aExternallyProvided));
+ }
+
+ @Override
+ public List<ProvidedInterface> getProvidedInterfaces() {
+ return Collections.unmodifiableList(_externallyProvided);
+ }
+
+ @Override
+ public Object get(String aKey) {
+ return _properties.get(aKey);
+ }
+
+ @Override
+ public void put(String aKey, Object aValue) {
+ _properties.put(aKey, aValue);
+ }
+
+ @Override
+ public void addRuntime(Component aComponent, Object aRuntime) {
+ _runtimes.put(aComponent.getName(), aRuntime);
+ }
+
+ @Override
+ public Object getRuntime(Component aComponent) {
+ return _runtimes.get(aComponent.getName());
+ }
+
+ @Override
+ public Object getRuntime(String aName) {
+ return _runtimes.get(aName);
+ }
+
+ @Override
+ synchronized public void publishInterface(ProvidedInterface aInterface,
+ Object aImplementation) {
+ _provided.put(aInterface, new ProvidedInterfaceImplementation(aInterface,
+ aImplementation));
+ }
+
+ @Override
+ public <T> T getInterfaceImplementation(ProvidedInterface aInterface,
+ Class<T> aType) {
+ if ( aInterface == null ) {
+ return null;
+ }
+ ProvidedInterfaceImplementation provided = _provided.get(aInterface);
+ if (provided == null) {
+ for (Scope parent : _parents) {
+ T impl = parent.getInterfaceImplementation(aInterface, aType);
+ if ( impl != null ) {
+ return impl;
+ }
+ }
+ return null;
+ } else {
+ return provided.getImplementation(aType);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+public interface NamedInterface {
+ /**
+ * Name for the interface.
+ */
+ String getName();
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.Collection;
+
+/**
+ * Represents an interface provided by a component.
+ * Different component objects should never share ProvidedInterface instances!
+ *
+ * @author Erik Brakkee
+ */
+public interface ProvidedInterface extends NamedInterface {
+
+ /**
+ * Returns the service type.
+ * @return Service type.
+ */
+ Class[] getInterfaceTypes();
+
+
+ /**
+ * Determines whether the current provided interface exceeds the given provided interface.
+ * In other words if it can provide at least what the given provided interface can provide.
+ * @param aInterface Interface to compare to.
+ * @return True if the current interface exceeds the given provided interface.
+ */
+ boolean covers(ProvidedInterface aInterface);
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+/**
+ * Represents a provided interface together with its implementation.
+ *
+ * @author Erik Brakkee
+ */
+class ProvidedInterfaceImplementation {
+
+ private ProvidedInterface _provided;
+ private Object _implementation;
+
+ /**
+ * Constructs the object.
+ * @param aProvided Provided interface.
+ * @param aImplementation Implementation.
+ */
+ public ProvidedInterfaceImplementation(ProvidedInterface aProvided, Object aImplementation) {
+ _provided = aProvided;
+ _implementation = aImplementation;
+ }
+
+ /**
+ * @return The provided interface.
+ */
+ public ProvidedInterface getProvided() {
+ return _provided;
+ }
+
+ /**
+ * @param <T> Expected type of the implementation.
+ * @param aType Type of the implementation.
+ * @return Implementation.
+ */
+ public <T> T getImplementation(Class<T> aType) {
+ return (T)_implementation;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+public interface RequiredInterface extends NamedInterface {
+
+ /**
+ * @return True iff the required interface is optional.
+ */
+ boolean isOptional();
+
+
+ /**
+ * Checks if the service is provided by a given provided interface.
+ * @param aInterface Provided interface.
+ * @return
+ */
+ boolean implementedBy(ProvidedInterface aInterface);
+
+ /**
+ * Sets the provider of this interface.
+ * @param aProvider Provider.
+ */
+ void setProvider(ProvidedInterface aProvider);
+
+ /**
+ * Gets the provider interface.
+ * @return Provider or null if not set.
+ */
+ ProvidedInterface getProvider();
+
+ /**
+ * Determines if the requirements of the current interface are at least those
+ * of the given required interface.
+ */
+ boolean covers(RequiredInterface aInterface);
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.List;
+
+/**
+ * A scope represents a set of running services and the runtime information for the
+ * started components and is (usually) the result of
+ * starting a container.
+ *
+ * @author Erik Brakkee
+ */
+public interface Scope {
+
+ /**
+ * Gets the provided interfaces by this scope.
+ * @return Provided interfaces.
+ */
+ List<ProvidedInterface> getProvidedInterfaces();
+
+ /**
+ * Adds a key value pair to the scope.
+ * @param aKey Key
+ * @param aValue Value.
+ */
+ void put(String aKey, Object aValue);
+
+ /**
+ * Retrieves a value for the key.
+ * @param aKey Key.
+ * @return Value.
+ */
+ Object get(String aKey);
+
+ /**
+ * Adds the runtime of a started component.
+ * @param aComponent Component.
+ * @param aRuntime Runtime.
+ */
+ void addRuntime(Component aComponent, Object aRuntime);
+
+ /**
+ * Publishes an implementation of a provided interface.
+ * @param aInterface Interface that is provided.
+ * @param aImplementation Implementation of the interface.
+ */
+ void publishInterface(ProvidedInterface aInterface, Object aImplementation);
+
+ /**
+ * Retrieves an implementation of a provided interface.
+ * @param aProvided P
+ * rovided interface. If it is null then null is returned.
+ * @param aType Type of implementation that is expected.
+ * @return Retrieved interface.
+ */
+ <T> T getInterfaceImplementation(ProvidedInterface aProvided, Class<T> aType );
+
+ /**
+ * Gets the runtime for a component.
+ * @param aComponent Component for which we want to get the runtime.
+ * @return Runtime.
+ */
+ Object getRuntime(Component aComponent);
+
+ /**
+ * Gets the runtime for a component based on the name of the component
+ * (excluding its context).
+ * @param aName Component name.
+ * @return Component name.
+ */
+ Object getRuntime(String aName);
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+/**
+ * Exception thrown when an error occurs in assembling the systems.
+ *
+ * @author Erik Brakkee
+ */
+public class SystemAssemblyException extends RuntimeException {
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message.
+ */
+ public SystemAssemblyException(String aMsg) {
+ super(aMsg);
+ }
+
+ /**
+ * Constructs the exception.
+ * @param aMsg Message
+ * @param aCause Cause.
+ */
+ public SystemAssemblyException(String aMsg, Throwable aCause) {
+ super(aMsg, aCause);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompositeEdgeFilter implements EdgeFilter {
+
+ private List<EdgeFilter> _filters;
+
+ public CompositeEdgeFilter() {
+ _filters = new ArrayList<EdgeFilter>();
+ }
+
+ public void add(EdgeFilter aFilter) {
+ _filters.add(aFilter);
+ }
+
+ @Override
+ public boolean isViolated(Edge aEdge) {
+ for (EdgeFilter filter: _filters) {
+ if ( filter.isViolated(aEdge) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+/**
+ * Represents an application-independent edge of a graph.
+ * Applications might choose to implement the Edge interface
+ * directly.
+ *
+ * @author Erik Brakkee
+ */
+public class DefaultEdge implements Edge {
+
+ private Node _from;
+ private Node _to;
+
+ public DefaultEdge(Node aFrom, Node aTo) {
+ _from = aFrom;
+ _to = aTo;
+ }
+
+ @Override
+ public Node getFrom() {
+ return _from;
+ }
+
+ @Override
+ public Node getTo() {
+ return _to;
+ }
+
+ @Override
+ public String toString() {
+ return "Edge(" + _from.getName() + ", " + _to.getName() + ")";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+/**
+ * Default application-independent node. Specific applications of the graph might
+ * implement the Node interface directly.
+ * @author Erik Brakkee
+ *
+ */
+public class DefaultNode implements Node {
+
+ private String _name;
+
+ /**
+ * Constructs the node.
+ * @param aName Node name.
+ */
+ public DefaultNode(String aName) {
+ _name = aName;
+ }
+
+ /**
+ * Returns the node name.
+ */
+ @Override
+ public String getName() {
+ return _name;
+ }
+
+ @Override
+ public boolean equals(Object aObj) {
+ if ( !(aObj instanceof Node)) {
+ return false;
+ }
+ Node node = (Node)aObj;
+ return _name.equals(node.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return _name.hashCode();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+/**
+ * Represents an edge of a graph.
+ *
+ * @author Erik Brakkee
+ */
+public interface Edge {
+
+ /**
+ * @return The from part of the edge.
+ */
+ Node getFrom();
+
+ /**
+ * @return The to part of the edge.
+ */
+ Node getTo();
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+import java.util.List;
+
+/**
+ * Edge factory used to extend a graph with new edges.
+ * @author Erik Brakkee
+ *
+ * @param <NodeType> Type of node.
+ */
+public interface EdgeFactory<NodeType extends Node> {
+ /**
+ * Computes a number of new edges to be added to the graph.
+ * @param aFrom From node.
+ * @param aTo To node.
+ * @return List of edges from the from to the to node.
+ */
+ List<Edge> create(NodeType aFrom, NodeType aTo);
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+public interface EdgeFilter {
+
+ boolean isViolated(Edge aEdge);
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+public class EdgeSelector<FromType extends Node, ToType extends Node> {
+
+ public static interface Selector<FromType extends Node, ToType extends Node> {
+ void execute(FromType aFrom, ToType aTo);
+ }
+
+ private Class<FromType> _fromType;
+ private Class<ToType> _toType;
+
+ public EdgeSelector(Class<FromType> aFrom, Class<ToType> aTo) {
+ _fromType = aFrom;
+ _toType = aTo;
+ }
+
+ public void execute(Selector<FromType,ToType> aSelector, Edge aEdge) {
+ if ( _fromType.isInstance(aEdge.getFrom()) && _toType.isInstance(aEdge.getTo())) {
+ aSelector.execute((FromType)aEdge.getFrom(), (ToType)aEdge.getTo());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a graph consisting of nodes and edges.
+ *
+ * @author Erik Brakkee
+ */
+public class Graph {
+
+ private List<Node> _nodes;
+ private List<Edge> _edges;
+
+ /**
+ * Constructs the graph.
+ */
+ public Graph() {
+ _nodes = new ArrayList<Node>();
+ _edges = new ArrayList<Edge>();
+ }
+
+ /**
+ * Adds a node.
+ *
+ * @param aNode
+ * Node to add.
+ * @throws IllegalArgumentException
+ * In case the node already exists. Node equality is checked
+ * using <code>equals</code>.
+ */
+ public void addNode(Node aNode) {
+ if (_nodes.contains(aNode)) {
+ throw new IllegalArgumentException("Node '" + aNode.getName()
+ + "' already exists");
+ }
+ _nodes.add(aNode);
+ }
+
+ /**
+ * Finds a node with the given name.
+ *
+ * @param aName
+ * Node name.
+ * @return Node or null if not found.
+ */
+ public Node findNode(String aName) {
+ for (Node node : _nodes) {
+ if (node.getName().equals(aName)) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes a node.
+ *
+ * @param aNode
+ * Node to remove.
+ * @return True iff the node was removed.
+ * @throws IllegalArgumentException
+ * In case there are edges of which the node is a part.
+ */
+ public boolean removeNode(Node aNode) {
+ if (!findOutgoing(aNode).isEmpty() || !findIncoming(aNode).isEmpty()) {
+ throw new IllegalArgumentException("Cannot remove node '"
+ + aNode.getName()
+ + "' because it is connected to one or more edges");
+ }
+ return _nodes.remove(aNode);
+ }
+
+ /**
+ * Adds a list of nodes.
+ *
+ * @param aNodes
+ * Nodes to add.
+ *
+ * @see #addNode(Node)
+ */
+ public void addNodes(List<Node> aNodes) {
+ for (Node node : aNodes) {
+ addNode(node);
+ }
+ }
+
+ /**
+ * Adds an edge.
+ *
+ * @param aEdge
+ * Edge to add.
+ * @throws IllegalArgumentException
+ * In case one of the nodes of the edges is not part of the
+ * graph or if the same edge (as determined by
+ * {@link #equals(Object)} is already a part of the graph.
+ */
+ public void addEdge(Edge aEdge) {
+ if (_edges.contains(aEdge)) {
+ throw new IllegalArgumentException("Edge '" + aEdge
+ + "' already exists");
+ }
+ if (!_nodes.contains(aEdge.getFrom())) {
+ throw new IllegalArgumentException("From node '" + aEdge.getFrom()
+ + "' from edge '" + aEdge + "' is not part of the graph");
+ }
+ if (!_nodes.contains(aEdge.getTo())) {
+ throw new IllegalArgumentException("To node '" + aEdge.getTo()
+ + "' from edge '" + aEdge + "' is not part of the graph");
+ }
+ _edges.add(aEdge);
+ }
+
+ /**
+ * Removes an edge.
+ * @param aEdge Edge to remove.
+ * @return True if the edge was removed.
+ */
+ public boolean removeEdge(Edge aEdge) {
+ return _edges.remove(aEdge);
+ }
+
+ /**
+ * Adds a number of edges.
+ * @param aEdges Edges to add.
+ */
+ public void addEdges(List<Edge> aEdges) {
+ for (Edge edge : aEdges) {
+ addEdge(edge);
+ }
+ }
+
+ /**
+ * Gets the nodes.
+ * @return Copy of the list of nodes of the graph.
+ */
+ public List<Node> getNodes() {
+ return new ArrayList<Node>(_nodes);
+ }
+
+ /**
+ * Gets the edges.
+ * @return Copy of the list of edges of the graph.
+ */
+ public List<Edge> getEdges() {
+ return new ArrayList<Edge>(_edges);
+ }
+
+ /**
+ * Extends the graph with edges using an edge factory. All combinations of
+ * nodes are passed to the factory which creates additional edges.
+ * @param aFactory Edge factory.
+ */
+ public void extend(EdgeFactory aFactory) {
+ for (Node from : _nodes) {
+ for (Node to : _nodes) {
+ _edges.addAll(aFactory.create(from, to));
+ }
+ }
+ }
+
+ /**
+ * Applies a filter to the graph for removing elements.
+ * @param aFilter Filter to apply.
+ */
+ public void applyFilter(EdgeFilter aFilter) {
+ for (Iterator<Edge> edge = _edges.iterator(); edge.hasNext(); ) {
+ if (aFilter.isViolated(edge.next())) {
+ edge.remove();
+ }
+ }
+ }
+
+ /**
+ * Finds all outgoing edges of a node. More specifically, finds
+ * all edges <code>e</code> for which <code>e.getFrom().getName() = aNode.getName()</code>.
+ * @param aNode Node for which to find outgoing edges.
+ * @return List of outgoing edges.
+ */
+ public List<Edge> findOutgoing(Node aNode) {
+ List<Edge> result = new ArrayList<Edge>();
+ for (Edge edge : _edges) {
+ if (edge.getFrom().getName().equals(aNode.getName())) {
+ result.add(edge);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Finds all incoming edges of a node.
+ * More specifically, finds
+ * all edges <code>e</code> for which <code>e.getTo().getName() = aNode.getName()</code>.
+ * @param aNode Node for which to find incoming edges.
+ * @return List of incoming edges.
+ */
+ public List<Edge> findIncoming(Node aNode) {
+ List<Edge> result = new ArrayList<Edge>();
+ for (Edge edge : _edges) {
+ if (edge.getTo().getName().equals(aNode.getName())) {
+ result.add(edge);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Implements a visitor design pattern.
+ * This loops over all nodes and all edges and invokes the appropriate visit
+ * methods on the visitor.
+ * @param aVisitor Visitor.
+ */
+ public void accept(Visitor aVisitor) {
+ List<Node> nodes = getNodes(); // copy to make sure the visitor can
+ // modify the
+ // list of nodes as part of the loop.
+ List<Edge> edges = getEdges(); // copy ..... (see above).
+
+ for (Node node : nodes) {
+ aVisitor.visitNode(node);
+ }
+ for (Edge edge : edges) {
+ aVisitor.visitEdge(edge);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+/**
+ * Represents a node in a graph.
+ * @author Erik Brakkee
+ */
+public interface Node {
+
+ /**
+ * Gets the node name uniquely identifying the node in the graph.
+ * @return Node name.
+ */
+ String getName();
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+/**
+ * Visitor of a graph.
+ * @author Erik Brakkee.
+ *
+ */
+public interface Visitor {
+
+ /**
+ * Visits a node. Called by {@link Graph#accept(Visitor)}.
+ * @param aNode Node to visit.
+ */
+ void visitNode(Node aNode);
+
+ /**
+ * Visits a node. Called by {@link Graph#accept(Visitor)}.
+ * @param aEdge Edge to visit.
+ */
+ void visitEdge(Edge aEdge);
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.List;
+
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.Graph;
+import org.wamblee.system.graph.Node;
+import org.wamblee.system.graph.Visitor;
+
+/**
+ * Visitor that checks whether all externally provided interfaces are actually provided
+ * by any of the internal components.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class CheckExternallyProvidedVisitor implements Visitor {
+
+ private Graph _graph;
+
+ /**
+ * Constructs the visitor.
+ * @param aGraph Component graph.
+ */
+ public CheckExternallyProvidedVisitor(Graph aGraph) {
+ _graph = aGraph;
+ }
+
+ @Override
+ public void visitEdge(Edge aEdge) {
+ // Empty.
+ }
+
+ @Override
+ public void visitNode(Node aNode) {
+ if ( aNode instanceof ExternalProvidedInterfaceNode) {
+ ExternalProvidedInterfaceNode provided = (ExternalProvidedInterfaceNode) aNode;
+ List<Edge> edges = _graph.findOutgoing(provided);
+ if ( edges.size() > 2 ) {
+ createDuplicateException("External provided interfaces has multiple internal matches", aNode, edges);
+ }
+ if ( edges.size() == 0 ) {
+ throw new SystemAssemblyException(aNode + ": external provded interface is not provided by any of the internal components");
+ }
+ }
+ }
+
+ private void createDuplicateException(String aMsg, Node aNode, List<Edge> edges) {
+ StringBuffer buf = new StringBuffer();
+ buf.append(aNode
+ + ": " + aMsg + ": ");
+ for (Edge edge : edges) {
+ buf.append(edge.getTo() + "/ ");
+ }
+ throw new SystemAssemblyException(buf.toString());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.Graph;
+import org.wamblee.system.graph.Node;
+import org.wamblee.system.graph.Visitor;
+
+/**
+ * Visitor that checks whether all required external interfaces of the container
+ * are provided.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class CheckExternallyRequiredVisitor implements Visitor {
+
+ private Log LOG = LogFactory.getLog(CheckExternallyRequiredVisitor.class);
+
+ private Graph _graph;
+
+ public CheckExternallyRequiredVisitor(Graph aGraph) {
+ _graph = aGraph;
+ }
+
+ @Override
+ public void visitEdge(Edge aEdge) {
+ // Empty.
+ }
+
+ @Override
+ public void visitNode(Node aNode) {
+ if (aNode instanceof ExternalRequiredInterfaceNode) {
+ ExternalRequiredInterfaceNode required = (ExternalRequiredInterfaceNode) aNode;
+ if (!required.getRequired().isOptional()
+ && required.getRequired().getProvider() == null) {
+ throw new SystemAssemblyException(aNode
+ + ": External required interface is not provided");
+ }
+
+ List<Edge> edges = _graph.findIncoming(aNode);
+
+ if (edges.isEmpty()) {
+ LOG.warn(aNode + ": Superfluous required interface");
+ }
+ for (Edge edge : edges) {
+ Node from = edge.getFrom();
+ assert from instanceof RequiredInterfaceNode;
+ RequiredInterfaceNode reqNode = (RequiredInterfaceNode)from;
+ if (!reqNode.getRequired().isOptional()
+ && required.getRequired().isOptional()) {
+ throw new SystemAssemblyException(
+ aNode
+ + ": externally required interface is optional but a corresponding internal required interface is mandatory: "
+ + reqNode);
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.List;
+
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.Graph;
+import org.wamblee.system.graph.Node;
+import org.wamblee.system.graph.Visitor;
+
+/**
+ * Visitor that checks whether required and provided interfaces are matched
+ * appropriately:
+ * <ul>
+ * <li>Each required interface is connected to at most one provided interface
+ * </li>
+ * <li>Required interfaces that are not optional must be connected to precisely
+ * one provided interface</li>
+ * </ul>
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class CheckRequiredProvidedMultiplicityVisitor implements Visitor {
+
+ private Graph _graph;
+
+ public CheckRequiredProvidedMultiplicityVisitor(Graph aGraph) {
+ _graph = aGraph;
+ }
+
+ @Override
+ public void visitEdge(Edge aEdge) {
+ // Empty
+ }
+
+ @Override
+ public void visitNode(Node aNode) {
+ if (aNode instanceof RequiredInterfaceNode) {
+ RequiredInterfaceNode required = (RequiredInterfaceNode) aNode;
+ List<Edge> edges = _graph.findOutgoing(aNode);
+ if (edges.size() > 1) {
+ createDuplicateException("Multiple providers of required interface found", aNode, edges);
+ }
+ if (edges.size() == 0 && !required.getRequired().isOptional()) {
+ throw new SystemAssemblyException(
+ aNode
+ + ": mandatpory required interface not provided by other components started earlier");
+ }
+ } else if ( aNode instanceof ExternalProvidedInterfaceNode) {
+ List<Edge> edges = _graph.findOutgoing(aNode);
+ if ( edges.size() > 1) {
+ createDuplicateException("multiple internal matches for externally provided interface", aNode, edges);
+ }
+ if ( edges.size() == 0 ) {
+ throw new SystemAssemblyException(aNode + ": external provided interface is not provided internally");
+ }
+ }
+ }
+
+ private void createDuplicateException(String aMsg, Node aNode, List<Edge> edges) {
+ StringBuffer buf = new StringBuffer();
+ buf.append(aNode
+ + ": " + aMsg + ": ");
+ for (Edge edge : edges) {
+ buf.append(edge.getTo() + "/ ");
+ }
+ throw new SystemAssemblyException(buf.toString());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.Graph;
+import org.wamblee.system.graph.Node;
+import org.wamblee.system.graph.Visitor;
+
+/**
+ * Checks whether the given component graph can be started in component
+ * order without any missing dependencies.
+ * @author Erik Brakkee
+ *
+ */
+public class CheckStartupDependenciesVisitor implements Visitor {
+
+ private Graph _graph;
+ private List<Node> _available;
+
+ /**
+ * Constructs the visitor.
+ * @param aGraph Graph.
+ */
+ public CheckStartupDependenciesVisitor(Graph aGraph) {
+ _graph = aGraph;
+ _available = new ArrayList<Node>();
+ }
+
+ @Override
+ public void visitEdge(Edge aEdge) {
+ // Empty
+ }
+
+ @Override
+ public void visitNode(Node aNode) {
+ List<Edge> edges = _graph.findOutgoing(aNode);
+
+ // check dependencies.
+ for (Edge edge: edges) {
+ Node dep = edge.getTo();
+ if ( !_available.contains(dep)) {
+ throw new SystemAssemblyException(aNode + ": required dependency '" + dep + "' was not started");
+ }
+ }
+
+ _available.add(aNode);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.general.Pair;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.graph.CompositeEdgeFilter;
+import org.wamblee.system.graph.DefaultEdge;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.Graph;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Represents a component graph and provides the bridge from the
+ * component model to a graph model. The graph model is easier
+ * to work with to implement specific actions and validations than
+ * the component model.
+ */
+public class ComponentGraph extends Graph {
+
+ private boolean _isLinked;
+ private CompositeEdgeFilter _edgeFilter;
+
+ /**
+ * Constructs an empty component graph.
+ */
+ public ComponentGraph() {
+ _isLinked = false;
+ _edgeFilter = new CompositeEdgeFilter();
+ }
+
+ /**
+ * Adds an externally required interface of a container.
+ * This should be called before any components of the container are
+ * added.
+ * @param aComponent Component requiring the interface.
+ * @param aInterface Required interface.
+ */
+ public void addRequiredInterface(Component aComponent, RequiredInterface aInterface) {
+ addNode(new ExternalRequiredInterfaceNode(aComponent, aInterface));
+ }
+
+ /**
+ * Adds an externally provided interface of a container.
+ * This should be called after all components of the container have been added.
+ * @param aComponent Component providing the interface.
+ * @param aInterface Provided interface.
+ */
+ public void addProvidedInterface(Component aComponent, ProvidedInterface aInterface) {
+ addNode(new ExternalProvidedInterfaceNode(aComponent, aInterface));
+ }
+
+ /**
+ * Validates the component graph.
+ */
+ public void validate() {
+ extend(new RequiredProvidedEdgeFactory());
+ applyFilter(_edgeFilter);
+ accept(new CheckRequiredProvidedMultiplicityVisitor(this));
+ accept(new CheckExternallyRequiredVisitor(this));
+ accept(new CheckExternallyProvidedVisitor(this));
+ accept(new CheckStartupDependenciesVisitor(this));
+ }
+
+ /**
+ * Links provided and required interfaces together in the component
+ * model based on the graph model.
+ */
+ public void link() {
+ if ( _isLinked ) {
+ return;
+ }
+ accept(new LinkVisitor());
+ _isLinked = true;
+ }
+
+ /**
+ * Finds a list of mappings of external provided interface to internal provided interface.
+ *
+ * @return List of pairs of external to internal interface.
+ */
+ public List<Pair<ProvidedInterface, ProvidedInterface>> findExternalProvidedInterfaceMapping() {
+ List<Pair<ProvidedInterface, ProvidedInterface>> result =
+ new ArrayList<Pair<ProvidedInterface,ProvidedInterface>>();
+ for (Edge edge: getEdges()) {
+ if ( edge.getFrom() instanceof ExternalProvidedInterfaceNode &&
+ edge.getTo() instanceof ProvidedInterfaceNode ) {
+ result.add(new Pair<ProvidedInterface,ProvidedInterface>(
+ ((ExternalProvidedInterfaceNode)edge.getFrom()).getProvided(),
+ ((ProvidedInterfaceNode)edge.getTo()).getProvided()
+ ));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Adds a component by adding required interfaces, components, and
+ * provided interfaces.
+ * @param aComponent Component to add.
+ */
+ public void addComponent(Component<?> aComponent) {
+ // Add required interfaces.
+ Node compNode = new ComponentNode(aComponent);
+ List<Node> requiredNodes = new ArrayList<Node>();
+ for (RequiredInterface required: aComponent.getRequiredInterfaces()) {
+ Node reqNode = new RequiredInterfaceNode(aComponent, required);
+ addNode(reqNode);
+ requiredNodes.add(reqNode);
+ }
+ // Add the component
+ addNode(compNode);
+
+ // Edges from component to required interface.
+ for (Node reqNode: requiredNodes) {
+ addEdge(new DefaultEdge(compNode, reqNode));
+ }
+
+ // Add provided interfaces
+ List<Node> providedNodes = new ArrayList<Node>();
+ for (ProvidedInterface provided: aComponent.getProvidedInterfaces()) {
+ Node provNode = new ProvidedInterfaceNode(aComponent, provided);
+ addNode(provNode);
+ providedNodes.add(provNode);
+ }
+
+ // Edges from provided interface to component
+ for (Node provNode: providedNodes) {
+ addEdge(new DefaultEdge(provNode, compNode));
+ }
+ }
+
+ public void addEdgeFilter(CompositeEdgeFilter aEdgeFilter) {
+ _edgeFilter.add(aEdgeFilter);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.core.Component;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Represents a component node.
+ * @author Erik Brakkee.
+ *
+ */
+public class ComponentNode implements Node {
+
+ private Component<?> _component;
+
+ public ComponentNode(Component<?> aComponent) {
+ _component = aComponent;
+ }
+
+ @Override
+ public String getName() {
+ return _component.getQualifiedName();
+ }
+
+ public Component<?> getComponent() {
+ return _component;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFilter;
+
+/**
+ * Filter used to explicitly connect required and provided interfaces within a
+ * container.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class ConnectExternalProvidedProvidedFilter implements EdgeFilter {
+
+ private String _externalProvided;
+ private String _component;
+ private String _provided;
+
+ public ConnectExternalProvidedProvidedFilter(String aExternalProvided, String aComponent,
+ String aProvided) {
+ _externalProvided = aExternalProvided;
+ _component = aComponent;
+ _provided = aProvided;
+ if ( _externalProvided == null ) {
+ throw new IllegalArgumentException("External provided interface name must be specified.");
+ }
+ if ( _component == null ) {
+ throw new IllegalArgumentException("Component name must be specified");
+ }
+ if ( _provided == null ) {
+ throw new IllegalArgumentException("Provided interface name of internal component must be specified");
+ }
+ }
+
+ @Override
+ public boolean isViolated(Edge aEdge) {
+ if (aEdge.getFrom() instanceof ExternalProvidedInterfaceNode
+ && aEdge.getTo() instanceof ProvidedInterfaceNode) {
+ return isViolated((ExternalProvidedInterfaceNode) aEdge.getFrom(),
+ (ProvidedInterfaceNode) aEdge.getTo());
+ }
+ return false;
+ }
+
+ private boolean isViolated(ExternalProvidedInterfaceNode aFrom,
+ ProvidedInterfaceNode aTo) {
+ if ( !aFrom.getName().equals(_externalProvided)) {
+ return false; // wrong provided interface.
+ }
+ if ( aTo.getComponent().getName().equals(_component) &&
+ aTo.getProvided().getName().equals(_provided) ) {
+ return false; // ok
+ }
+ return true;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFilter;
+
+/**
+ * Filter used to explicitly connect required and provided interfaces within a
+ * container.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class ConnectRequiredExternallyRequiredEdgeFilter implements EdgeFilter {
+
+ private String _client;
+ private String _required;
+ private String _externalRequired;
+
+ public ConnectRequiredExternallyRequiredEdgeFilter(String aClient, String aRequired,
+ String aExternalRequired) {
+ _client = aClient;
+ _required = aRequired;
+ _externalRequired = aExternalRequired;
+ if ( _client == null ) {
+ throw new IllegalArgumentException("Client component must be specified");
+ }
+ if ( _required == null ) {
+ throw new IllegalArgumentException("Required interface must be specified");
+ }
+ if ( _externalRequired == null ) {
+ throw new IllegalArgumentException("External required interface must be specified");
+ }
+ }
+
+ @Override
+ public boolean isViolated(Edge aEdge) {
+ if (aEdge.getFrom() instanceof RequiredInterfaceNode
+ && aEdge.getTo() instanceof ExternalRequiredInterfaceNode) {
+ return isViolated((RequiredInterfaceNode) aEdge.getFrom(),
+ (ExternalRequiredInterfaceNode) aEdge.getTo());
+ }
+ return false;
+ }
+
+ private boolean isViolated(RequiredInterfaceNode aFrom,
+ ExternalRequiredInterfaceNode aTo) {
+ if ( !aFrom.getComponent().getName().equals(_client)) {
+ return false; // wrong component.
+ }
+ if ( !(_required == null || aFrom.getRequired().getName().equals(_required))) {
+ return false; // wrong interface
+ }
+ if ( !aTo.getRequired().getName().equals(_externalRequired)) {
+ return true; // wrong externally required interface.
+ }
+
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFilter;
+
+/**
+ * Filter used to explicitly connect required and provided interfaces within a
+ * container.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class ConnectRequiredProvidedEdgeFilter implements EdgeFilter {
+
+ private String _client;
+ private String _required;
+ private String _server;
+ private String _provided;
+
+ public ConnectRequiredProvidedEdgeFilter(String aClient, String aRequired,
+ String aServer, String aProvided) {
+ _client = aClient;
+ _required = aRequired;
+ _server = aServer;
+ _provided = aProvided;
+ if ( _client == null ) {
+ throw new IllegalArgumentException("Client component must be specified");
+ }
+ }
+
+ @Override
+ public boolean isViolated(Edge aEdge) {
+ if (aEdge.getFrom() instanceof RequiredInterfaceNode
+ && aEdge.getTo() instanceof ProvidedInterfaceNode) {
+ return isViolated((RequiredInterfaceNode) aEdge.getFrom(),
+ (ProvidedInterfaceNode) aEdge.getTo());
+ }
+ return false;
+ }
+
+ private boolean isViolated(RequiredInterfaceNode aFrom,
+ ProvidedInterfaceNode aTo) {
+ if (_client.equals(aFrom.getComponent().getName())
+ && (_required == null || _required.equals(aFrom.getRequired()
+ .getName()))) {
+ // From part matches.
+ if ( _server == null ) {
+ return true; // all connections are eliminated
+ }
+ if (_server.equals(aTo.getComponent().getName())
+ && (_provided == null || _provided.equals(aTo.getProvided()
+ .getName()))) {
+ // to part matches also
+ return false;
+ }
+ else {
+ // From matches and to doesn't so edgefilter is violated.
+ return true;
+ }
+ } else {
+ // From part does not match, restriction does not apply.
+ return false;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Represents an external provided interface of a container.
+ * @author Erik Brakkee
+ *
+ */
+public class ExternalProvidedInterfaceNode implements Node {
+
+ private Component _component;
+ private ProvidedInterface _provided;
+
+ public ExternalProvidedInterfaceNode(Component aComponent, ProvidedInterface aProvided) {
+ _component = aComponent;
+ _provided = aProvided;
+ }
+
+ @Override
+ public String getName() {
+ return _provided.getName();
+ }
+
+ public Component getComponent() {
+ return _component;
+ }
+
+ public ProvidedInterface getProvided() {
+ return _provided;
+ }
+
+ @Override
+ public String toString() {
+ return _component.getQualifiedName() + ":" + _provided;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Represents an externally required interface of a container.
+ * @author Erik Brakkee
+ *
+ */
+public class ExternalRequiredInterfaceNode implements Node {
+
+ private Component _component;
+ private RequiredInterface _required;
+
+ public ExternalRequiredInterfaceNode(Component aComponent, RequiredInterface aRequired) {
+ _component = aComponent;
+ _required = aRequired;
+ }
+
+ @Override
+ public String getName() {
+ return _required.getName();
+ }
+
+ public Component getComponent() {
+ return _component;
+ }
+
+ public RequiredInterface getRequired() {
+ return _required;
+ }
+
+ @Override
+ public String toString() {
+ return _component.getQualifiedName() + ":" + _required;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.system.graph.DefaultEdge;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.Node;
+import org.wamblee.system.graph.Visitor;
+
+/**
+ * Visitor that creates links between required and provided interfaces as
+ * described by the edges in the graph.
+ *
+ * Specically it links together (1) required and provided interfaces of internal component
+ * of a container and (2) the providers of externally required interfaces and internal required
+ * interfaces.
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class LinkVisitor implements Visitor {
+
+ @Override
+ public void visitEdge(Edge aEdge) {
+ Node from = aEdge.getFrom();
+ Node to = aEdge.getTo();
+ if (from instanceof RequiredInterfaceNode) {
+ RequiredInterfaceNode required = (RequiredInterfaceNode) from;
+ if (to instanceof ProvidedInterfaceNode) {
+
+ ProvidedInterfaceNode provided = (ProvidedInterfaceNode) to;
+ required.getRequired().setProvider(provided.getProvided());
+
+ } else if (to instanceof ExternalRequiredInterfaceNode) {
+ ExternalRequiredInterfaceNode external = (ExternalRequiredInterfaceNode) to;
+ ProvidedInterface provider = external.getRequired()
+ .getProvider();
+ if (provider == null && !required.getRequired().isOptional()) {
+ throw new SystemAssemblyException("Mandatory interface '"
+ + from + "' is not provided.");
+ }
+ if ( provider != null ) {
+ required.getRequired().setProvider(provider);
+ }
+ }
+ } else if (from instanceof ProvidedInterfaceNode) {
+ // Provided interfaces can only be published after the system has
+ // started.
+ }
+ }
+
+ @Override
+ public void visitNode(Node aNode) {
+ // Empty.
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Provided interface node.
+ * @author Erik Brakkee
+ */
+public class ProvidedInterfaceNode implements Node {
+
+ private Component _component;
+ private ProvidedInterface _provided;
+
+ public ProvidedInterfaceNode(Component aComponent, ProvidedInterface aProvided) {
+ _component = aComponent;
+ _provided = aProvided;
+ }
+
+ @Override
+ public String getName() {
+ return _component.getQualifiedName() + ":" + _provided.getName();
+ }
+
+ public ProvidedInterface getProvided() {
+ return _provided;
+ }
+
+ public Component getComponent() {
+ return _component;
+ }
+
+ @Override
+ public String toString() {
+ return _component.getQualifiedName() + ":" + _provided;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Required interface node.
+ * @author Erik Brakkee
+ *
+ */
+public class RequiredInterfaceNode implements Node {
+
+ private Component _component;
+ private RequiredInterface _required;
+
+ public RequiredInterfaceNode(Component aComponent, RequiredInterface aRequired) {
+ _component = aComponent;
+ _required = aRequired;
+ }
+
+ @Override
+ public String getName() {
+ return _component.getQualifiedName() + ":" + _required.getName();
+ }
+
+ public RequiredInterface getRequired() {
+ return _required;
+ }
+
+ public Component getComponent() {
+ return _component;
+ }
+
+ @Override
+ public String toString() {
+ return _component.getQualifiedName() + ":" + _required;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.system.graph.DefaultEdge;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFactory;
+import org.wamblee.system.graph.Node;
+
+/**
+ * Factory that creates edges between required and provided interfaces.
+ * Speciflcally it creates:
+ * <ul>
+ * <li> Edges between provided and required interfaces of a container. </li>
+ * <li> Edges between required and externally required interfaces </li>
+ * <li> Edges between externally provided and provided interfaces.
+ * </ul>
+ *
+ * @author Erik Brakkee
+ *
+ */
+public class RequiredProvidedEdgeFactory implements EdgeFactory {
+
+ public RequiredProvidedEdgeFactory() {
+ // Empty.
+ }
+
+ @Override
+ public List<Edge> create(Node aFrom, Node aTo) {
+ List<Edge> result = new ArrayList<Edge>();
+ if (aFrom instanceof RequiredInterfaceNode) {
+ RequiredInterfaceNode required = (RequiredInterfaceNode) aFrom;
+ if (aTo instanceof ProvidedInterfaceNode) {
+
+ ProvidedInterfaceNode provided = (ProvidedInterfaceNode) aTo;
+ if (required.getRequired()
+ .implementedBy(provided.getProvided())) {
+ result.add(new DefaultEdge(required, provided));
+ }
+ } else if (aTo instanceof ExternalRequiredInterfaceNode) {
+ ExternalRequiredInterfaceNode external = (ExternalRequiredInterfaceNode) aTo;
+ if ( external.getRequired().covers(required.getRequired())) {
+ result.add(new DefaultEdge(required, external));
+ }
+ }
+ } else if ( aFrom instanceof ProvidedInterfaceNode) {
+ ProvidedInterfaceNode provided = (ProvidedInterfaceNode)aFrom;
+ if ( aTo instanceof ExternalProvidedInterfaceNode) {
+ ExternalProvidedInterfaceNode external = (ExternalProvidedInterfaceNode)aTo;
+ if (provided.getProvided().covers(external.getProvided())) {
+ result.add(new DefaultEdge(external, provided));
+ }
+ }
+ }
+ return result;
+ }
+
+}
--- /dev/null
+/**
+ * <h1>Graph component package </h1>
+ *
+ * The graph component package provides the representation of a number of
+ * components {@link Component}
+ * their required and provided interfaces ({@link RequiredInterface} and
+ * {@link ProvidedInterface}), and how these are connected together.
+ *
+ * This package provides the bridge between the component model and the
+ * representation of the components and their connections in a graph.
+ * The component package provides various algorithms, filters, and
+ * validations that are required for the implementation of a container.
+ *
+ * The main abstractions are:
+ * <ul>
+ * <li> {@link ComponentGraph}: A graph of components. This provides the logic
+ * for creating a graph based on components. </li>
+ * <li> {@link ComponentNode}: A node representing a component. </li>
+ * <li> {@link RequiredInterfaceNode}: A node representing a required interface of a component. </li>
+ * <li> {@link ProvidedInterfaceNode}: A node repesenting a provided interface of a component. </li>
+ * <li> {@link ExternalRequiredInterfaceNode}: A node representing a required interface of a container </li>
+ * <li> {@link ExternalProvidedInterfaceNode}: A node representing a provided interface of a container </li>
+ * </ul>
+ *
+ */
+package org.wamblee.system.graph.component;
+
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+
--- /dev/null
+/**
+ * <h1>Graph package</h1>
+ *
+ * The graph package provides a general very simple abstraction of a graph.
+ * It was developed for the IOC container to represent dependences between
+ * components through their required and provided interfaces.
+ *
+ * The graph package supports a number of simple graph traversal operations, graph
+ * extension operations, and a visitor pattern.
+ *
+ * The main abstractions are:
+ * <ul>
+ * <li> {@link Graph}: The graph itself. </li>
+ * <li> {@link Node}: A node of a graph. </li>
+ * <li> {@link Edge}: An edge of a graph> </li>
+ * <li> {@link Visitor}: A visitor for implementing various operations on a graph. </li>
+ * <li> {@link EdgeFactory}: For extending the graph with new edges </li>
+ * </ul>
+ */
+package org.wamblee.system.graph;
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.DefaultScope;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.test.EventTracker;
+
+import junit.framework.TestCase;
+
+public class AdapterTestCase extends TestCase {
+
+ protected Scope _scope;
+ static EventTracker<String> EVENT_TRACKER;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ EVENT_TRACKER = new EventTracker<String>();
+ _scope = new DefaultScope(new ProvidedInterface[0]);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.container.Container;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.test.AssertionUtils;
+
+public class ClassAdapterTest extends AdapterTestCase {
+
+
+ public void testSimpleConstructorInjection() {
+ ClassConfiguration x1Config = new ClassConfiguration(X1.class);
+ x1Config.getObjectConfig().getSetterConfig().initAllSetters();
+ x1Config.getConstructorConfig().getParameters().setValue(0, "hello");
+ ClassConfiguration x4Config = new ClassConfiguration(X4.class);
+ x4Config.getObjectConfig().getSetterConfig().initAllSetters();
+
+ ClassAdapter x1Adapter = new ClassAdapter("x1", x1Config);
+ ClassAdapter x4Adapter = new ClassAdapter("x4", x4Config);
+
+ Container container = new Container("top", new Component[] {
+ x1Adapter, x4Adapter
+ }, new ProvidedInterface[0], new RequiredInterface[0]);
+
+ Scope scope = container.start();
+ AssertionUtils.assertEquals(new String[] { "x1(hello)", "x4(x1)" },
+ EVENT_TRACKER.getEvents(Thread.currentThread()).toArray());
+
+ Object obj1 = scope.getRuntime(x1Adapter);
+ assertTrue(obj1 instanceof X1);
+ Object obj4 = scope.getRuntime(x4Adapter);
+ assertTrue(obj4 instanceof X4);
+
+ X1 x1 = (X1) obj1;
+ X4 x4 = (X4) obj4;
+
+ assertSame(x1, x4.getX1());
+ }
+
+ public void testConstructorAndSetterInjection() {
+ ClassConfiguration x1Config = new ClassConfiguration(X1.class);
+ x1Config.getObjectConfig().getSetterConfig().initAllSetters();
+ x1Config.getConstructorConfig().getParameters().setValue(0, "hello");
+ ClassConfiguration x4Config = new ClassConfiguration(X4.class);
+ x4Config.getObjectConfig().getSetterConfig().initAllSetters();
+ ClassConfiguration x8Config = new ClassConfiguration(X8.class);
+ x8Config.getObjectConfig().getSetterConfig().initAllSetters();
+
+ ClassAdapter x1Adapter = new ClassAdapter("x1", x1Config);
+ ClassAdapter x4Adapter = new ClassAdapter("x4", x4Config);
+ ClassAdapter x8Adapter = new ClassAdapter("x8", x8Config);
+
+ Container container = new Container("top", new Component[] {
+ x1Adapter, x4Adapter, x8Adapter
+ }, new ProvidedInterface[0], new RequiredInterface[0]);
+
+ Scope scope = container.start();
+ AssertionUtils.assertEquals(new String[] { "x1(hello)", "x4(x1)", "x8(x1)", "x8.setX4(x4)" },
+ EVENT_TRACKER.getEvents(Thread.currentThread()).toArray());
+
+ Object obj1 = scope.getRuntime(x1Adapter);
+ assertTrue(obj1 instanceof X1);
+ Object obj4 = scope.getRuntime(x4Adapter);
+ assertTrue(obj4 instanceof X4);
+ Object obj8 = scope.getRuntime(x8Adapter);
+
+ X1 x1 = (X1) obj1;
+ X4 x4 = (X4) obj4;
+ X8 x8 = (X8) obj8;
+
+ assertSame(x4, x8.getX4());
+ assertSame(x1, x8.getX1());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.RequiredInterfaceComparator;
+import org.wamblee.test.AssertionUtils;
+
+public class ClassConfigurationTest extends AdapterTestCase {
+
+ public void testConstructorConfig() {
+ ClassConfiguration classConfig = new ClassConfiguration(X1.class);
+
+ ConstructorConfiguration config = classConfig.getConstructorConfig()
+ .greedy();
+
+ ProvidedInterface provided = new DefaultProvidedInterface("arg",
+ String.class);
+ List<RequiredInterface> required = classConfig.getRequiredInterfaces();
+
+ assertEquals(1, required.size());
+ assertFalse(required.get(0).isOptional());
+
+ required.get(0).setProvider(provided);
+
+ _scope.publishInterface(provided, "hello");
+ classConfig.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x1(hello)" },
+ AdapterTestCase.EVENT_TRACKER.getEvents(Thread.currentThread())
+ .toArray());
+ }
+
+ public void testConstructorConfigWithSetters() {
+ ClassConfiguration classConfig = new ClassConfiguration(X7.class);
+
+ classConfig.getConstructorConfig().select(Boolean.class);
+ classConfig.getObjectConfig().getSetterConfig().initAllSetters().values("setPort").setValue(0, 10);
+
+ ProvidedInterface providedBoolean = new DefaultProvidedInterface("boolean",
+ Boolean.class);
+ ProvidedInterface providedHost = new DefaultProvidedInterface("host", String.class);
+ List<RequiredInterface> required = classConfig.getRequiredInterfaces();
+
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(2, required.size());
+ assertEquals("arg0", required.get(0).getName());
+
+ required.get(0).setProvider(providedBoolean);
+ required.get(1).setProvider(providedHost);
+
+ _scope.publishInterface(providedBoolean, true);
+ _scope.publishInterface(providedHost, "host.name.org");
+
+ Object obj = classConfig.create(_scope);
+ assertTrue(obj instanceof X7);
+ X7 x7 = (X7)obj;
+ assertNotNull(x7.getBoolean());
+ assertTrue(x7.getBoolean());
+ assertNull(x7.getHost());
+ assertNull(x7.getPort());
+
+ classConfig.inject(_scope, obj);
+
+ assertEquals("host.name.org", x7.getHost());
+ assertEquals(10, x7.getPort().intValue());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.util.List;
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.test.AssertionUtils;
+
+public class ConstructorConfigurationTest extends AdapterTestCase {
+
+ public void testGreedyUnique() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X1.class)
+ .greedy();
+ ProvidedInterface provided = new DefaultProvidedInterface("arg",
+ String.class);
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+
+ assertEquals(1, required.size());
+ assertFalse(required.get(0).isOptional());
+
+ required.get(0).setProvider(provided);
+
+ _scope.publishInterface(provided, "hello");
+ config.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x1(hello)" }, AdapterTestCase.EVENT_TRACKER
+ .getEvents(Thread.currentThread()).toArray());
+ }
+
+ public void testGreedyNonUnique() {
+ try {
+ ConstructorConfiguration config = new ConstructorConfiguration(
+ X2.class).greedy();
+ } catch (SystemAssemblyException e) {
+ // e.printStackTrace();
+ return;
+ }
+ fail();
+ }
+
+ public void testSpecificConstructor() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X2.class)
+ .select(String.class);
+ ProvidedInterface provided = new DefaultProvidedInterface("arg",
+ String.class);
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+
+ assertEquals(1, required.size());
+ required.get(0).setProvider(provided);
+
+ _scope.publishInterface(provided, "hello");
+ config.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x2(hello)" }, AdapterTestCase.EVENT_TRACKER
+ .getEvents(Thread.currentThread()).toArray());
+ }
+
+ public void testSetValue() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X1.class)
+ .greedy();
+ config.getParameters().setValue(0, "bla");
+
+ config.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x1(bla)" }, AdapterTestCase.EVENT_TRACKER
+ .getEvents(Thread.currentThread()).toArray());
+ }
+
+ public void testOptionalValueProvided() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X1.class)
+ .greedy();
+ config.getParameters().setOptional(0);
+ ProvidedInterface provided = new DefaultProvidedInterface("arg",
+ String.class);
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+
+ assertEquals(1, required.size());
+ required.get(0).setProvider(provided);
+
+ _scope.publishInterface(provided, "hello");
+ config.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x1(hello)" }, AdapterTestCase.EVENT_TRACKER
+ .getEvents(Thread.currentThread()).toArray());
+ }
+
+ public void testOptionalValueMissing() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X1.class)
+ .greedy();
+ config.getParameters().setOptional(0);
+ assertTrue(config.getRequiredInterfaces().get(0).isOptional());
+
+ config.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x1(null)" }, AdapterTestCase.EVENT_TRACKER
+ .getEvents(Thread.currentThread()).toArray());
+ }
+
+ public void testIgnoredNonPublic() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X3.class)
+ .greedy();
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ assertEquals(0, config.getParameters().getTypes().length);
+ }
+
+ public void testNonPublicConstructor() {
+ ConstructorConfiguration config = new ConstructorConfiguration(X3.class)
+ .setNonPublic(true).greedy();
+ ProvidedInterface provided = new DefaultProvidedInterface("arg",
+ String.class);
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+
+ assertEquals(1, required.size());
+ assertFalse(required.get(0).isOptional());
+
+ required.get(0).setProvider(provided);
+
+ _scope.publishInterface(provided, "hello");
+ config.create(_scope);
+
+ AssertionUtils.assertEquals(new String[] { "x3(hello)" }, AdapterTestCase.EVENT_TRACKER
+ .getEvents(Thread.currentThread()).toArray());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import org.wamblee.system.core.Scope;
+import org.wamblee.test.AssertionUtils;
+
+public class DefaultContainerTest extends AdapterTestCase {
+
+ public void testConstructorInjection() {
+ ClassConfiguration x1Config = new ClassConfiguration(X1.class);
+ x1Config.getConstructorConfig().getParameters().setValue(0, "hello");
+ x1Config.getObjectConfig().getSetterConfig().initAllSetters();
+
+ ClassConfiguration x4Config = new ClassConfiguration(X4.class);
+ x4Config.getObjectConfig().getSetterConfig().initAllSetters();
+
+ DefaultContainer container = new DefaultContainer("top").addComponent(
+ "x1", x1Config).addComponent("x4", x4Config);
+
+ Scope scope = container.start();
+ AssertionUtils.assertEquals(new String[] { "x1(hello)", "x4(x1)" },
+ EVENT_TRACKER.getEvents(Thread.currentThread()).toArray());
+
+ Object obj = scope.getRuntime("x1");
+ assertTrue(obj instanceof X1);
+ obj = scope.getRuntime("x4");
+ assertTrue(obj instanceof X4);
+ }
+
+ public void testConstructorInjectionAndSetterInjection() {
+ ClassConfiguration x1Config = new ClassConfiguration(X1.class);
+ x1Config.getConstructorConfig().getParameters().setValue(0, "hello");
+ x1Config.getObjectConfig().getSetterConfig().initAllSetters();
+
+ X8 x8 = new X8(null);
+ EVENT_TRACKER.clear();
+
+ ClassConfiguration x4Config = new ClassConfiguration(X4.class);
+ x4Config.getObjectConfig().getSetterConfig().initAllSetters();
+
+ ObjectConfiguration x8Config = new ObjectConfiguration(X8.class);
+ x8Config.getSetterConfig().initAllSetters();
+
+ DefaultContainer container = new DefaultContainer("top").addComponent(
+ "x1", x1Config).addComponent("x4", x4Config).addComponent("x8", x8, x8Config);
+
+ Scope scope = container.start();
+ AssertionUtils.assertEquals(new String[] { "x1(hello)", "x4(x1)",
+ "x8.setX4(x4)"},
+ EVENT_TRACKER.getEvents(Thread.currentThread()).toArray());
+
+ Object obj1 = scope.getRuntime("x1");
+ assertTrue(obj1 instanceof X1);
+ Object obj4 = scope.getRuntime("x4");
+ assertTrue(obj4 instanceof X4);
+ Object obj8 = scope.getRuntime("x8");
+ assertSame(x8, obj8);
+ assertSame(obj4, x8.getX4());
+ }
+
+ public void testWrongObjectType() {
+ final DefaultContainer container = new DefaultContainer("top");
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.addComponent("x", "y", new ObjectConfiguration(Integer.class));
+ }
+ }, IllegalArgumentException.class);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.wamblee.system.container.Container;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.RequiredInterfaceComparator;
+import org.wamblee.system.core.Scope;
+import org.wamblee.test.AssertionUtils;
+
+public class ObjectAdapterTest extends AdapterTestCase {
+
+ public void testSetterInjection() {
+
+ ClassConfiguration x1Config = new ClassConfiguration(X1.class);
+ x1Config.getObjectConfig().getSetterConfig().initAllSetters();
+ x1Config.getConstructorConfig().getParameters().setValue(0, "hello");
+
+ ClassConfiguration x4Config = new ClassConfiguration(X4.class);
+ x4Config.getObjectConfig().getSetterConfig().initAllSetters();
+ ObjectConfiguration x8Config = new ObjectConfiguration(X8.class);
+ x8Config.getSetterConfig().initAllSetters();
+
+ X1 x1 = new X1();
+ X8 x8 = new X8(x1);
+
+ ClassAdapter x1Adapter = new ClassAdapter("x1", x1Config);
+ ClassAdapter x4Adapter = new ClassAdapter("x4", x4Config);
+ ObjectAdapter x8Adapter = new ObjectAdapter("x8", x8, x8Config);
+
+ Container container = new Container("top", new Component[] {
+ x1Adapter, x4Adapter, x8Adapter
+ }, new ProvidedInterface[0], new RequiredInterface[0]);
+
+ EVENT_TRACKER.clear();
+ Scope scope = container.start();
+ AssertionUtils.assertEquals(new String[] { "x1(hello)", "x4(x1)", "x8.setX4(x4)" },
+ EVENT_TRACKER.getEvents(Thread.currentThread()).toArray());
+
+ Object obj1 = scope.getRuntime(x1Adapter);
+ assertTrue(obj1 instanceof X1);
+ Object obj4 = scope.getRuntime(x4Adapter);
+ assertTrue(obj4 instanceof X4);
+ Object obj8 = scope.getRuntime(x8Adapter);
+ assertSame(x8, obj8);
+
+ X4 x4 = (X4) obj4;
+
+
+ assertSame(x4, x8.getX4());
+ assertSame(x1, x8.getX1());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.RequiredInterfaceComparator;
+import org.wamblee.test.AssertionUtils;
+
+public class SetterConfigurationTest extends AdapterTestCase {
+
+ public void testOneSetter() {
+ SetterConfiguration config = new SetterConfiguration(X5.class);
+ config.initAllSetters();
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ assertEquals(1, required.size());
+ assertEquals("setValue.0", required.get(0).getName());
+
+ ProvidedInterface provided = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided);
+ _scope.publishInterface(provided, "hello");
+
+ X5 obj = new X5();
+ assertNull(obj.getValue());
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getValue());
+ }
+
+ public void testPrivateSetter() {
+ SetterConfiguration config = new SetterConfiguration(X5.class);
+ config.setNonPublic(true);
+ config.initAllSetters();
+ List<RequiredInterface> required = new ArrayList<RequiredInterface>(
+ config.getRequiredInterfaces());
+ Collections.sort(required, new Comparator<RequiredInterface>() {
+ @Override
+ public int compare(RequiredInterface aO1, RequiredInterface aO2) {
+ return aO1.getName().compareTo(aO2.getName());
+ }
+ });
+ assertEquals(2, required.size());
+ assertEquals("setValue.0", required.get(0).getName());
+ assertEquals("setXyz.0", required.get(1).getName());
+
+ ProvidedInterface providedString = new DefaultProvidedInterface(
+ "janse", String.class);
+ assertTrue(required.get(0).implementedBy(providedString));
+ required.get(0).setProvider(providedString);
+ _scope.publishInterface(providedString, "hello");
+
+ ProvidedInterface providedInt = new DefaultProvidedInterface("xxx",
+ Integer.class);
+ assertTrue(required.get(1).implementedBy(providedInt));
+ required.get(1).setProvider(providedInt);
+ _scope.publishInterface(providedInt, 100);
+
+ X5 obj = new X5();
+ assertNull(obj.getValue());
+ assertNull(obj.getXyz());
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getValue());
+ assertEquals(100, obj.getXyz().intValue());
+ }
+
+ public void testInheritance() {
+ SetterConfiguration config = new SetterConfiguration(X9.class);
+ config.setNonPublic(true);
+ config.initAllSetters();
+ List<RequiredInterface> required = new ArrayList<RequiredInterface>(
+ config.getRequiredInterfaces());
+ Collections.sort(required, new Comparator<RequiredInterface>() {
+ @Override
+ public int compare(RequiredInterface aO1, RequiredInterface aO2) {
+ return aO1.getName().compareTo(aO2.getName());
+ }
+ });
+ assertEquals(3, required.size());
+ assertEquals("setFlag.0", required.get(0).getName());
+ assertEquals("setValue.0", required.get(1).getName());
+ assertEquals("setXyz.0", required.get(2).getName());
+
+ ProvidedInterface providedBoolean = new DefaultProvidedInterface(
+ "janse", Boolean.class);
+ assertTrue(required.get(0).implementedBy(providedBoolean));
+ required.get(0).setProvider(providedBoolean);
+ _scope.publishInterface(providedBoolean, true);
+
+ ProvidedInterface providedString = new DefaultProvidedInterface(
+ "janse", String.class);
+ assertTrue(required.get(1).implementedBy(providedString));
+ required.get(1).setProvider(providedString);
+ _scope.publishInterface(providedString, "hello");
+
+ ProvidedInterface providedInt = new DefaultProvidedInterface("xxx",
+ Integer.class);
+ assertTrue(required.get(2).implementedBy(providedInt));
+ required.get(2).setProvider(providedInt);
+ _scope.publishInterface(providedInt, 100);
+
+ X9 obj = new X9();
+ assertNull(obj.getValue());
+ assertNull(obj.getXyz());
+ assertFalse(obj.isFlag());
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getValue());
+ assertEquals(100, obj.getXyz().intValue());
+ assertTrue(obj.isFlag());
+ }
+
+ public void testMultipleSetters() {
+ SetterConfiguration config = new SetterConfiguration(X6.class).initAllSetters();
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(2, required.size());
+ assertEquals("setHost.0", required.get(0).getName());
+ assertEquals("setPort.0", required.get(1).getName());
+
+ ProvidedInterface provided0 = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided0);
+ _scope.publishInterface(provided0, "hello");
+
+ ProvidedInterface provided1 = new DefaultProvidedInterface("port",
+ Integer.class);
+ required.get(1).setProvider(provided1);
+ _scope.publishInterface(provided1, 10);
+
+ X6 obj = new X6();
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getHost());
+ assertEquals(10, obj.getPort().intValue());
+ }
+
+ public void testInvokeWrongType() {
+ final SetterConfiguration config = new SetterConfiguration(X5.class).initAllSetters();
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ assertEquals(1, required.size());
+ assertEquals("setValue.0", required.get(0).getName());
+
+ ProvidedInterface provided = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided);
+ _scope.publishInterface(provided, "hello");
+
+ final X6 obj = new X6();
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ config.inject(_scope, obj);
+ }
+ }, IllegalArgumentException.class);
+ }
+
+ public void testSetExplicitValue() {
+ SetterConfiguration config = new SetterConfiguration(X5.class).initAllSetters();
+ config.values("setValue").setValue(0, "bladibla");
+
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ assertEquals(0, required.size());
+
+ X5 obj = new X5();
+ assertNull(obj.getValue());
+ config.inject(_scope, obj);
+ assertEquals("bladibla", obj.getValue());
+ }
+
+ public void testClear() {
+ SetterConfiguration config = new SetterConfiguration(X6.class);
+ config.clear();
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(0, required.size());
+
+ X6 obj = new X6();
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+
+ config.inject(_scope, obj);
+
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+ }
+
+ public void testAddByName() {
+ SetterConfiguration config = new SetterConfiguration(X6.class);
+ config.clear().add("setHost");
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(1, required.size());
+ assertEquals("setHost.0", required.get(0).getName());
+
+ ProvidedInterface provided0 = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided0);
+ _scope.publishInterface(provided0, "hello");
+
+ X6 obj = new X6();
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getHost());
+ assertNull(obj.getPort());
+ }
+
+ public void testAddByType() {
+ SetterConfiguration config = new SetterConfiguration(X6.class);
+ config.clear().addSetter(String.class);
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(1, required.size());
+ assertEquals("setHost.0", required.get(0).getName());
+
+ ProvidedInterface provided0 = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided0);
+ _scope.publishInterface(provided0, "hello");
+
+ X6 obj = new X6();
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getHost());
+ assertNull(obj.getPort());
+ }
+
+ public void testAddPrivate() {
+ X5 obj = new X5();
+ final SetterConfiguration config = new SetterConfiguration(X5.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ config.add("xyz");
+ }
+ }, IllegalArgumentException.class);
+
+ config.setNonPublic(true);
+ config.clear();
+ config.add("setXyz");
+ assertEquals(1, config.getRequiredInterfaces().size());
+ }
+
+ public void testAddNonExisting() {
+ final SetterConfiguration config = new SetterConfiguration(X6.class);
+ config.clear();
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ config.add("bladibla");
+ }
+ }, IllegalArgumentException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ config.addSetter(DataSource.class);
+ }
+ }, IllegalArgumentException.class);
+ }
+
+ public void testAddByTypeNonUnique() {
+ final SetterConfiguration config = new SetterConfiguration(X11.class);
+ config.clear();
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ config.addSetter(String.class);
+ }
+ }, IllegalArgumentException.class);
+ }
+
+ public void testRemove() {
+ SetterConfiguration config = new SetterConfiguration(X6.class).initAllSetters();
+ config.remove("setPort");
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(1, required.size());
+ assertEquals("setHost.0", required.get(0).getName());
+
+ ProvidedInterface provided0 = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided0);
+ _scope.publishInterface(provided0, "hello");
+
+ X6 obj = new X6();
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getHost());
+ assertNull(obj.getPort());
+ }
+
+ public void testRemoveByMethodObject() throws NoSuchMethodException {
+ SetterConfiguration config = new SetterConfiguration(X6.class).initAllSetters();
+ config.remove(X6.class.getMethod("setPort", Integer.class));
+ List<RequiredInterface> required = config.getRequiredInterfaces();
+ Collections.sort(required, new RequiredInterfaceComparator());
+ assertEquals(1, required.size());
+ assertEquals("setHost.0", required.get(0).getName());
+
+ ProvidedInterface provided0 = new DefaultProvidedInterface("janse",
+ String.class);
+ required.get(0).setProvider(provided0);
+ _scope.publishInterface(provided0, "hello");
+
+ X6 obj = new X6();
+ assertNull(obj.getHost());
+ assertNull(obj.getPort());
+
+ config.inject(_scope, obj);
+ assertEquals("hello", obj.getHost());
+ assertNull(obj.getPort());
+ }
+
+ public void testRemoveNonExisting() {
+ final SetterConfiguration config = new SetterConfiguration(X6.class);
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ config.remove("bladibla");
+ }
+ }, IllegalArgumentException.class);
+ }
+
+ public void testOverridingSetters() {
+ SetterConfiguration config = new SetterConfiguration(X10.class).initAllSetters();
+ assertEquals(2, config.getRequiredInterfaces().size());
+ List<Method> methods = config.getSetters();
+ assertEquals(2, methods.size());
+ for (Method method: methods) {
+ assertEquals(X10.class, method.getDeclaringClass());
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X1 {
+ public X1() {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x1()");
+ }
+
+ public X1(String aValue) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x1(" + aValue + ")");
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.wamblee.system.adapters;
+
+public class X10 extends X6 {
+
+
+ @Override
+ public void setHost(String aHost) {
+ super.setHost(aHost);
+ }
+
+ @Override
+ public String getHost() {
+ return super.getHost();
+ }
+
+ @Override
+ public Integer getPort() {
+ return super.getPort();
+ }
+
+ @Override
+ public void setPort(Integer aPort) {
+ super.setPort(aPort);
+ }
+
+}
--- /dev/null
+package org.wamblee.system.adapters;
+
+public class X11 {
+
+
+ public void setX(String aValue) {
+
+ }
+
+ public void setY(String aValue) {
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X2 {
+ public X2(Integer aInteger) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x2(" + aInteger + ")");
+ }
+
+ public X2(String aValue) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x2(" + aValue + ")");
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X3 {
+ public X3() {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x3()");
+ }
+
+ protected X3(String aValue) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x3(" + aValue + ")");
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X4 {
+
+ private X1 _x1;
+
+ public X4(X1 aX1) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x4(x1)");
+ _x1 = aX1;
+ }
+
+ public X1 getX1() {
+ return _x1;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X5 {
+
+ private String _value;
+ private Integer _xyz;
+
+ public X5() {
+
+ }
+
+ public void setValue(String aValue) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x5.setValue(" + aValue + ")");
+ _value = aValue;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ public void doSomething() {
+ // Empty.
+ }
+
+ private void setXyz(int aXyz) {
+ _xyz = aXyz;
+ }
+
+ public Integer getXyz() {
+ return _xyz;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X6 {
+
+ private String _host;
+ private Integer _port;
+
+ public X6() {
+
+ }
+
+ public Integer getPort() {
+ return _port;
+ }
+
+ public void setPort(Integer aPort) {
+ _port = aPort;
+ }
+
+ public String getHost() {
+ return _host;
+ }
+
+ public void setHost(String aHost) {
+ _host = aHost;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X7 {
+
+ private String _host;
+ private Integer _port;
+ private Boolean _boolean;
+
+ public X7(Boolean aBoolean) {
+ _boolean = aBoolean;
+ }
+
+ public Boolean getBoolean() {
+ return _boolean;
+ }
+
+ public Integer getPort() {
+ return _port;
+ }
+
+ public void setPort(Integer aPort) {
+ _port = aPort;
+ }
+
+ public String getHost() {
+ return _host;
+ }
+
+ public void setHost(String aHost) {
+ _host = aHost;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.adapters;
+
+public class X8 {
+
+ private X1 _x1;
+ private X4 _x4;
+
+ public X8(X1 aX1) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x8(x1)");
+ _x1 = aX1;
+ }
+
+ public void setX4(X4 aX4) {
+ AdapterTestCase.EVENT_TRACKER.eventOccurred("x8.setX4(x4)");
+ _x4 = aX4;
+ }
+
+ public X4 getX4() {
+ return _x4;
+ }
+
+ public X1 getX1() {
+ return _x1;
+ }
+}
--- /dev/null
+package org.wamblee.system.adapters;
+
+public class X9 extends X5 {
+
+ private boolean _flag;
+
+ public X9() {
+
+ }
+
+ public boolean isFlag() {
+ return _flag;
+ }
+
+ public void setFlag(boolean aFlag) {
+ _flag = aFlag;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.components;
+
+import java.util.Properties;
+
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DerbyDatabase;
+import org.wamblee.system.components.ORMappingConfig.DatabaseType;
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.Scope;
+
+public class DatabaseComponent extends AbstractComponent<Database> {
+
+ private static ProvidedInterface DB_PROPS = new DefaultProvidedInterface("dbProps", Properties.class);
+
+ private Database _database;
+
+ public DatabaseComponent(String aName, Database aDatabase) {
+ super(aName);
+ _database = aDatabase;
+ addProvidedInterface(DB_PROPS);
+ }
+
+ @Override
+ protected Database doStart(Scope aScope) {
+ _database.start();
+
+ Properties props = new Properties();
+ if ( _database instanceof DerbyDatabase ) {
+ props.put("database.type", DatabaseType.DERBY.toString());
+ } else {
+ throw new IllegalArgumentException("Unknown database type " + _database);
+ }
+ //props.put("database.driver", _database.getDriverClassName());
+ props.put("database.url", _database.getJdbcUrl());
+ props.put("database.username", _database.getUsername());
+ props.put("database.password", _database.getPassword());
+
+ addInterface(DB_PROPS, props, aScope);
+ return _database;
+ }
+
+ @Override
+ protected void doStop(Database aRuntime) {
+ _database.stop();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.components;
+
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.system.container.Container;
+
+public class DatabaseComponentFactory {
+
+ public static void addDatabaseConfig(Container aContainer) {
+ try {
+ aContainer.addComponent(new DatabaseComponent("db",
+ DatabaseBuilder.getDatabase()));
+ } catch (Exception e) {
+ throw new RuntimeException("Could not add database configuration",
+ e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.container;
+
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.test.EventTracker;
+
+public class Application extends AbstractComponent<Object> {
+ public static RequiredInterface[] required(boolean aOptional,
+ String aPrefix) {
+ return new RequiredInterface[] {
+ new DefaultRequiredInterface(aPrefix + "string", String.class,
+ aOptional),
+ new DefaultRequiredInterface(aPrefix + "integer",
+ Integer.class, aOptional) };
+ }
+
+ public static RequiredInterface[] required(boolean aOptional) {
+ return required(aOptional, "");
+ }
+
+ private EventTracker<String> _tracker;
+ private String _string;
+ private Integer _integer;
+ private double _random;
+
+ public Application() {
+ this("application");
+ }
+
+ public Application(String aName) {
+ this(aName, "");
+ }
+
+ public Application(String aName, String aPrefix) {
+ super(aName, new ProvidedInterface[0], required(false,
+ aPrefix));
+ _random = Math.random();
+ }
+
+ public Application(boolean aIsOptinal) {
+ super("application", new ProvidedInterface[0], required(true, ""));
+ }
+
+ public Application(EventTracker<String> aTracker) {
+ this();
+ _tracker = aTracker;
+ }
+
+ @Override
+ public Object doStart(Scope aScope) {
+ track("start." + getName());
+ _string = aScope.getInterfaceImplementation(getRequiredInterfaces()
+ .get(0).getProvider(), String.class);
+ _integer = aScope.getInterfaceImplementation(getRequiredInterfaces()
+ .get(1).getProvider(), Integer.class);
+ return _random;
+ }
+
+ public String getString() {
+ return _string;
+ }
+
+ public Integer getInteger() {
+ return _integer;
+ }
+
+ @Override
+ public void doStop(Object aRuntime) {
+ track("stop." + getName());
+ if (_random != (Double) aRuntime) {
+ throw new IllegalArgumentException("Wrong runtime: expected "
+ + _random + " but got " + aRuntime);
+ }
+ }
+
+ private void track(String aString) {
+ if (_tracker == null) {
+ return;
+ }
+ _tracker.eventOccurred(aString);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.container;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.wamblee.general.Pair;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.DefaultScope;
+import org.wamblee.system.core.Environment;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.core.StringComponent;
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.test.AssertionUtils;
+import org.wamblee.test.EventTracker;
+
+public class ContainerTest extends TestCase {
+
+ private EventTracker<String> _tracker;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ _tracker = new EventTracker<String>();
+ }
+
+ private static class MyMultiple implements Serializable, Runnable {
+ @Override
+ public void run() {
+ // Empty
+ }
+ }
+
+ private List<Pair<ProvidedInterface, Component>> createProvidedInput(
+ ProvidedInterface[] aProvided, Component aProvider) {
+ List<Pair<ProvidedInterface, Component>> result = new ArrayList<Pair<ProvidedInterface, Component>>();
+ for (ProvidedInterface provided : aProvided) {
+ result.add(new Pair<ProvidedInterface, Component>(provided,
+ aProvider));
+ }
+ return result;
+ }
+
+ public void testEnvironmentApplication() {
+ Environment environment = new Environment(_tracker);
+ Application application = new Application(_tracker);
+ Container container = new Container("root", new Component[] {
+ environment, application }, new ProvidedInterface[0],
+ new RequiredInterface[0]);
+ Scope scope = container.start();
+ assertTrue(container.isSealed());
+ AssertionUtils.assertEquals(new String[] { "start.environment",
+ "start.application" }, _tracker.getEvents(
+ Thread.currentThread()).toArray(new String[0]));
+ assertEquals(0, scope.getProvidedInterfaces().size());
+
+ assertEquals(environment.getString(), application.getString());
+ assertEquals(environment.getInteger(), application.getInteger());
+
+ }
+
+ public void testEnvironmentApplicationSimpleConstructor() {
+ Environment environment = new Environment(_tracker);
+ Application application = new Application(_tracker);
+ Container container = new Container("root").addComponent(environment)
+ .addComponent(application);
+
+ Scope scope = container.start();
+ AssertionUtils.assertEquals(new String[] { "start.environment",
+ "start.application" }, _tracker.getEvents(
+ Thread.currentThread()).toArray(new String[0]));
+ assertEquals(0, scope.getProvidedInterfaces().size());
+
+ assertEquals(environment.getString(), application.getString());
+ assertEquals(environment.getInteger(), application.getInteger());
+
+ }
+
+ public void testApplicationEnvironment() {
+ try {
+ Component<?> environment = new Environment();
+ Component<?> application = new Application();
+ Container container = new Container("root", new Component[] {
+ application, environment }, new ProvidedInterface[0],
+ new RequiredInterface[0]);
+ container.start();
+ } catch (SystemAssemblyException e) {
+ // e.printStackTrace();
+ return;
+ }
+ fail();
+ }
+
+ public void testComposite() {
+ Component<?> environment = new Environment(_tracker);
+ Component<?> application = new Application(_tracker);
+ assertEquals(0, _tracker.getEventCount());
+
+ Container system = new Container("all", new Component[] { environment,
+ application }, new ProvidedInterface[0],
+ new RequiredInterface[0]);
+ Scope runtime = system.start();
+ List<RequiredInterface> required = system.getRequiredInterfaces();
+ assertEquals(0, required.size());
+ List<ProvidedInterface> provided = system.getProvidedInterfaces();
+ assertEquals(0, provided.size());
+
+ AssertionUtils.assertEquals(new String[] { "start.environment",
+ "start.application" }, _tracker.getEvents(
+ Thread.currentThread()).toArray(new String[0]));
+ _tracker.clear();
+
+ system.stop(runtime);
+ AssertionUtils.assertEquals(new String[] { "stop.application",
+ "stop.environment" }, _tracker
+ .getEvents(Thread.currentThread()).toArray(new String[0]));
+
+ }
+
+ public void testCompositeWithWrongProvidedInfo() {
+ try {
+ Component<?> environment = new Environment();
+ Component<?> application = new Application();
+ Container system = new Container("all", new Component[] {
+ environment, application },
+ new ProvidedInterface[] { new DefaultProvidedInterface(
+ "float", Float.class) },
+ new DefaultRequiredInterface[0]);
+ system.validate();
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testCompositeRequiredInterfaceNotProvided() {
+ try {
+ Component<?> environment = new Environment();
+ Component<?> application = new Application();
+ Container system = new Container("all", new Component[] {
+ environment, application }, new ProvidedInterface[0],
+ new RequiredInterface[] { new DefaultRequiredInterface(
+ "string", String.class) });
+ system.start();
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testCompositeWithSuperfluousRequiredInfo() {
+ Component<?> environment = new Environment();
+ Component<?> application = new Application();
+ Container system = new Container("all", new Component[] { environment,
+ application }, new ProvidedInterface[0],
+ new RequiredInterface[] { new DefaultRequiredInterface("float",
+ Float.class) });
+ system.getRequiredInterfaces().get(0).setProvider(
+ new DefaultProvidedInterface("hallo", Float.class));
+ system.start();
+ List<RequiredInterface> required = system.getRequiredInterfaces();
+ assertEquals(1, required.size());
+ List<ProvidedInterface> provided = system.getProvidedInterfaces();
+ assertEquals(0, provided.size());
+ }
+
+ public void testCompositeWithExternalDependencesNotProvided() {
+ try {
+ Component<?> application = new Application();
+
+ Container system = new Container("all",
+ new Component[] { application }, new ProvidedInterface[0],
+ application.getRequiredInterfaces().toArray(
+ new RequiredInterface[0]));
+ system.start();
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testDuplicateComponent() {
+ try {
+ Component<?> comp1 = new Application();
+ Component<?> comp2 = new Application();
+ Container system = new Container("top");
+ system.addComponent(comp1).addComponent(comp2);
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testInconsistentHierarchy() {
+ try {
+ Component<?> comp = new Application();
+ Container system = new Container("top").addComponent(comp);
+ Container system2 = new Container("top2").addComponent(comp);
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testCompositeWithExternalDependencesProvided() {
+
+ Component<?> environment = new Environment();
+ Component<?> application = new Application();
+ Container system = new Container("all",
+ new Component[] { application }, new ProvidedInterface[0],
+ application.getRequiredInterfaces().toArray(
+ new RequiredInterface[0]));
+ environment.start(new DefaultScope(new ProvidedInterface[0]));
+ system.getRequiredInterfaces().get(0).setProvider(
+ environment.getProvidedInterfaces().get(0));
+ system.getRequiredInterfaces().get(1).setProvider(
+ environment.getProvidedInterfaces().get(1));
+
+ system.start();
+ List<RequiredInterface> required = system.getRequiredInterfaces();
+ assertEquals(2, required.size());
+ List<ProvidedInterface> provided = system.getProvidedInterfaces();
+ assertEquals(0, provided.size());
+
+ }
+
+ public void testAmbiguousInterfaces() {
+ try {
+ Component<?> environment1 = new Environment();
+ Component<?> environment2 = new Environment();
+ Component<?> application = new Application();
+ Container container = new Container("root", new Component[] {
+ environment1, environment2, application },
+ new ProvidedInterface[0], new RequiredInterface[0]);
+ container.start();
+
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testIncompleteRequirements() {
+ try {
+ Component<?> application = new Application();
+ Container system = new Container("all",
+ new Component[] { application }, new ProvidedInterface[0],
+ new RequiredInterface[0]);
+ system.start();
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testEnvironmentApplicationRollbackOnException()
+ throws Exception {
+ Environment environment = new Environment(_tracker);
+ Application application = new Application() {
+ @Override
+ public Object doStart(Scope aScope) {
+ throw new RuntimeException();
+ }
+ };
+
+ try {
+ Container container = new Container("root", new Component[] {
+ environment, application }, new ProvidedInterface[0],
+ new RequiredInterface[0]);
+
+ container.start();
+ } catch (RuntimeException e) {
+ AssertionUtils.assertEquals(new String[] { "start.environment",
+ "stop.environment" }, _tracker.getEvents(
+ Thread.currentThread()).toArray(new String[0]));
+ return;
+ }
+ fail();
+ }
+
+ public void testEnvironmentApplicationRollbackOnExceptionWithExceptionOnStop()
+ throws Exception {
+
+ Environment environment = new Environment(_tracker);
+ // Application 1 will throw an exception while stopping.
+ Application application1 = new Application("app1") {
+ @Override
+ public void doStop(Object aRuntime) {
+ throw new RuntimeException();
+ }
+ };
+
+ // application 2 will throw an exception while starting
+ Application application2 = new Application("app2") {
+ public Object doStart(Scope aScope) {
+ throw new RuntimeException();
+ }
+ };
+
+ try {
+ Container container = new Container("root", new Component[] {
+ environment, application1, application2 },
+ new ProvidedInterface[0], new RequiredInterface[0]);
+
+ container.start();
+ } catch (RuntimeException e) {
+ AssertionUtils.assertEquals(new String[] { "start.environment",
+ "stop.environment" }, _tracker.getEvents(
+ Thread.currentThread()).toArray(new String[0]));
+ return;
+ }
+ fail();
+ }
+
+ public void testOptionalRequiredInterfaceProvidedOptionalInternal() {
+ Application application = new Application(true);
+ Container container = new Container("top",
+ new Component[] { application }, new ProvidedInterface[0],
+ Application.required(true));
+ Environment env = new Environment();
+ container.getRequiredInterfaces().get(0).setProvider(
+ env.getProvidedInterfaces().get(0));
+ container.getRequiredInterfaces().get(1).setProvider(
+ env.getProvidedInterfaces().get(1));
+ Scope external = new DefaultScope(env.getProvidedInterfaces());
+ env.start(external);
+
+ container.start(external);
+ assertSame(env.getProvidedInterfaces().get(0), container
+ .getRequiredInterfaces().get(0).getProvider());
+ assertSame(env.getProvidedInterfaces().get(1), container
+ .getRequiredInterfaces().get(1).getProvider());
+ assertSame(env.getProvidedInterfaces().get(0), application
+ .getRequiredInterfaces().get(0).getProvider());
+ assertSame(env.getProvidedInterfaces().get(1), application
+ .getRequiredInterfaces().get(1).getProvider());
+ }
+
+ public void testOptionalRequiredInterfaceNotProvidedOptionalInternal() {
+ Application application = new Application(true);
+ Container container = new Container("top",
+ new Component[] { application }, new ProvidedInterface[0],
+ Application.required(true));
+ Environment env = new Environment();
+ container.getRequiredInterfaces().get(0).setProvider(
+ env.getProvidedInterfaces().get(0));
+ Scope external = new DefaultScope(new ProvidedInterface[0]);
+ external.publishInterface(env.getProvidedInterfaces().get(0), env
+ .getString());
+ container.start(external);
+ assertSame(env.getProvidedInterfaces().get(0), container
+ .getRequiredInterfaces().get(0).getProvider());
+ assertNull(container.getRequiredInterfaces().get(1).getProvider());
+ assertSame(env.getProvidedInterfaces().get(0), application
+ .getRequiredInterfaces().get(0).getProvider());
+ assertNull(application.getRequiredInterfaces().get(1).getProvider());
+ }
+
+ public void testOptionalRequiredInterfaceProvidedMandatoryInternal() {
+ Application application = new Application();
+ Container container = new Container("top",
+ new Component[] { application }, new ProvidedInterface[0],
+ Application.required(true));
+ Environment env = new Environment();
+ container.getRequiredInterfaces().get(0).setProvider(
+ env.getProvidedInterfaces().get(0));
+ container.getRequiredInterfaces().get(1).setProvider(
+ env.getProvidedInterfaces().get(1));
+ try {
+ container.start();
+ } catch (SystemAssemblyException e) {
+ return;
+ }
+ fail();
+ }
+
+ public void testSealed() {
+ final Container container = new Container("xx");
+ assertFalse(container.isSealed());
+ container.start();
+ assertTrue(container.isSealed());
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.addComponent(new Application());
+ }
+ }, SystemAssemblyException.class);
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectRequiredProvided("x", "y", "a", "b");
+ }
+ }, SystemAssemblyException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalRequired("x", "y", "a");
+ }
+ }, SystemAssemblyException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalProvided("x", "y", "z");
+ }
+ }, SystemAssemblyException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.addProvidedInterface(new DefaultProvidedInterface(
+ "xx", String.class));
+ }
+ }, SystemAssemblyException.class);
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.addRequiredInterface(new DefaultRequiredInterface(
+ "xx", String.class));
+ }
+ }, SystemAssemblyException.class);
+ }
+
+ public void testRestriction() {
+ Environment env1 = new Environment("env1");
+ Environment env2 = new Environment("env2");
+ Application app = new Application("app");
+ Container container = new Container("top").addComponent(env1)
+ .addComponent(env2).addComponent(app);
+ container.connectRequiredProvided("app", null, "env1", null);
+ container.start();
+ assertEquals(env1.getString(), app.getString());
+ assertEquals(env1.getInteger(), app.getInteger());
+ assertFalse(env2.getString().equals(app.getString()));
+ assertFalse(env2.getInteger().equals(app.getInteger()));
+ }
+
+ public void testRestrictionWithFromAndToInterfaceName() {
+ Environment env1 = new Environment("env1");
+ Environment env2 = new Environment("env2");
+ Application app = new Application("app");
+ Container container = new Container("top").addComponent(env1)
+ .addComponent(env2).addComponent(app);
+ container.connectRequiredProvided("app", app.getRequiredInterfaces()
+ .get(0).getName(), "env1", env1.getProvidedInterfaces().get(0)
+ .getName());
+ container.connectRequiredProvided("app", app.getRequiredInterfaces()
+ .get(1).getName(), "env2", env2.getProvidedInterfaces().get(1)
+ .getName());
+ container.start();
+ assertEquals(env1.getString(), app.getString());
+ assertEquals(env2.getInteger(), app.getInteger());
+ assertFalse(env2.getString().equals(app.getString()));
+ assertFalse(env1.getInteger().equals(app.getInteger()));
+ }
+
+ public void testRestrictionWrongComponentNames() {
+ Environment env1 = new Environment("env1");
+ Environment env2 = new Environment("env2");
+ Application app = new Application("app");
+ final Container container = new Container("top").addComponent(env1)
+ .addComponent(env2).addComponent(app);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectRequiredProvided("app2", null, "env1", null);
+ }
+ }, SystemAssemblyException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectRequiredProvided("app", null, "env3", null);
+ }
+ }, SystemAssemblyException.class);
+ }
+
+ public void testRestrictionWrongInterfaceNames() {
+ final Environment env1 = new Environment("env1");
+ Environment env2 = new Environment("env2");
+ final Application app = new Application("app");
+ final Container container = new Container("top").addComponent(env1)
+ .addComponent(env2).addComponent(app);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectRequiredProvided("app", app
+ .getRequiredInterfaces().get(0).getName()
+ + "xxx", "env1", null);
+ }
+ }, SystemAssemblyException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectRequiredProvided("app", null, "env1", env1
+ .getProvidedInterfaces().get(0).getName()
+ + "yyy");
+ }
+ }, SystemAssemblyException.class);
+ }
+
+ public void testProvidedInDifferentScopes() {
+ // Scoping problem occurred. Externally and internally provided
+ // components clashed
+ // because unique id generation in the scope was wrong.
+
+ StringComponent str = new StringComponent("string");
+ Application app = new Application("app");
+ Container container = new Container("top").addComponent(str)
+ .addComponent(app);
+ container.addRequiredInterface(new DefaultRequiredInterface("integer",
+ Integer.class));
+
+ ProvidedInterface provided = new DefaultProvidedInterface("hallo",
+ Integer.class);
+ container.getRequiredInterfaces().get(0).setProvider(provided);
+
+ Scope external = new DefaultScope(new ProvidedInterface[0]);
+ external.publishInterface(provided, 100);
+ Scope scope = container.start(external);
+ }
+
+ public void testProvidedInterfaces() {
+ Environment env = new Environment(_tracker);
+ Container envcontainer = new Container("0").addComponent(env)
+ .addProvidedInterface(
+ new DefaultProvidedInterface("string", String.class))
+ .addProvidedInterface(
+ new DefaultProvidedInterface("integer", Integer.class));
+ Scope scope = envcontainer.start();
+
+ AssertionUtils.assertEquals(new String[] { "start.environment" },
+ _tracker.getEvents(Thread.currentThread()).toArray(
+ new String[0]));
+
+ envcontainer.stop(scope);
+ }
+
+ public void testCoupleTwoContainers() {
+ Environment env = new Environment(_tracker);
+ Container envcontainer = new Container("0").addComponent(env)
+ .addProvidedInterface(
+ new DefaultProvidedInterface("string", String.class))
+ .addProvidedInterface(
+ new DefaultProvidedInterface("integer", Integer.class));
+
+ Application app = new Application(_tracker);
+ Container appcontainer = new Container("1").addComponent(app)
+ .addRequiredInterface(
+ new DefaultRequiredInterface("string", String.class))
+ .addRequiredInterface(
+ new DefaultRequiredInterface("integer", Integer.class));
+
+ Container top = new Container("top");
+ top.addComponent(envcontainer).addComponent(appcontainer);
+
+ top.start();
+ AssertionUtils.assertEquals(new String[] { "start.environment",
+ "start.application" }, _tracker.getEvents(
+ Thread.currentThread()).toArray(new String[0]));
+
+ }
+
+ public void testNonUniqueRequiredInterface() {
+ final Container container = new Container("top");
+ container.addRequiredInterface(new DefaultRequiredInterface("i",
+ Integer.class));
+ container.addRequiredInterface(new DefaultRequiredInterface("x",
+ String.class));
+ container.addRequiredInterface(new DefaultRequiredInterface("y",
+ String.class));
+
+ Application app = new Application("1");
+ container.addComponent(app);
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.start();
+ }
+ }, SystemAssemblyException.class);
+
+ container.connectExternalRequired("1", app.getRequiredInterfaces().get(
+ 0).getName(), "y");
+
+ ProvidedInterface i = new DefaultProvidedInterface("i", Integer.class);
+ ProvidedInterface x = new DefaultProvidedInterface("x", String.class);
+ ProvidedInterface y = new DefaultProvidedInterface("y", String.class);
+
+ Scope externalScope = new DefaultScope(new ProvidedInterface[0]);
+
+ externalScope.publishInterface(i, 100);
+ externalScope.publishInterface(x, "x-value");
+ externalScope.publishInterface(y, "y-value");
+
+ container.getRequiredInterfaces().get(0).setProvider(i);
+ container.getRequiredInterfaces().get(1).setProvider(x);
+ container.getRequiredInterfaces().get(2).setProvider(y);
+
+ Scope runtime = container.start(externalScope);
+
+ assertEquals("y-value", app.getString());
+
+ }
+
+ public void testNonUniqueRequiredInterfaceWrongNames() {
+ final Container container = new Container("top");
+ container.addRequiredInterface(new DefaultRequiredInterface("i",
+ Integer.class));
+ container.addRequiredInterface(new DefaultRequiredInterface("x",
+ String.class));
+ container.addRequiredInterface(new DefaultRequiredInterface("y",
+ String.class));
+
+ final Application app = new Application("1");
+ container.addComponent(app);
+
+ // wrong component name.
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalRequired("2", "x", "y");
+ }
+ }, SystemAssemblyException.class);
+
+ // Wrong interface name of component.
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalRequired("1", app
+ .getRequiredInterfaces().get(0).getName()
+ + "xxx", "y");
+ }
+ }, SystemAssemblyException.class);
+
+ // Wrong external interface name of container
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalRequired("1", app
+ .getRequiredInterfaces().get(0).getName(), "z");
+ }
+ }, SystemAssemblyException.class);
+ }
+
+ public void testNonUniqueProvidedInterface() {
+
+ final Container container = new Container("top")
+ .addProvidedInterface(new DefaultProvidedInterface("external",
+ String.class));
+ Environment env1 = new Environment("env1");
+ Environment env2 = new Environment("env2");
+
+ container.addComponent(env1);
+ container.addComponent(env2);
+
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.start();
+ }
+ }, SystemAssemblyException.class);
+
+ // now choose env2
+
+ container.connectExternalProvided(container.getProvidedInterfaces()
+ .get(0).getName(), env2.getName(), env2.getProvidedInterfaces()
+ .get(0).getName());
+
+ Scope scope = container.start();
+
+ // check the value of the provided interface of the container
+
+ String value = scope.getInterfaceImplementation(container
+ .getProvidedInterfaces().get(0), String.class);
+ assertNotNull(value);
+ assertEquals(value, env2.getString());
+ assertFalse(value.equals(env1.getString()));
+ }
+
+ public void testNonUniqueProvidedInterfaceWrongNames() {
+
+ final Container container = new Container("top")
+ .addProvidedInterface(new DefaultProvidedInterface("external",
+ String.class));
+ final Environment env1 = new Environment("env1");
+ final Environment env2 = new Environment("env2");
+
+ container.addComponent(env1);
+ container.addComponent(env2);
+
+ // Wrong external provided interface name
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalProvided(container
+ .getProvidedInterfaces().get(0).getName()
+ + "xx", "env1", env1.getProvidedInterfaces().get(0)
+ .getName());
+ }
+ }, SystemAssemblyException.class);
+
+ // Wrong provided interface name.
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalProvided(container
+ .getProvidedInterfaces().get(0).getName(), "env1", env1
+ .getProvidedInterfaces().get(0).getName()
+ + "xx");
+ }
+ }, SystemAssemblyException.class);
+
+ // Wrong provided component
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ container.connectExternalProvided(container
+ .getProvidedInterfaces().get(0).getName(), "env3", env1
+ .getProvidedInterfaces().get(0).getName());
+ }
+ }, SystemAssemblyException.class);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import junit.framework.TestCase;
+
+public class AbstractComponentTest extends TestCase {
+
+ public void testNotAllInterfacesStarted() {
+ try {
+ Component<?> component = new AbstractComponent<Object>("xx",
+ new ProvidedInterface[] { new DefaultProvidedInterface(
+ "xxx", String.class) }, new RequiredInterface[0]) {
+ @Override
+ protected Object doStart(Scope aScope) {
+ // Empty, not starting service.
+ return null;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ // Empty.
+ }
+ };
+ component.start(new DefaultScope(component.getProvidedInterfaces()));
+ } catch (SystemAssemblyException e) {
+ //e.printStackTrace();
+ return;
+ }
+ fail();
+ }
+
+ public void testUnexpectedServicesStarted() {
+ try {
+ Component<?> component = new AbstractComponent<Object>("xx",
+ new ProvidedInterface[0], new RequiredInterface[0]) {
+ @Override
+ protected Object doStart(Scope aScope) {
+ addInterface(new DefaultProvidedInterface("x", Integer.class), 100, aScope);
+ return null;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ // Empty.
+ }
+ };
+ component.start(new DefaultScope(component.getProvidedInterfaces()));
+ } catch (SystemAssemblyException e) {
+ //e.printStackTrace();
+ return;
+ }
+ fail();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.io.Serializable;
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+
+import junit.framework.TestCase;
+
+public class DefaultInterfaceDescriptorTest extends TestCase {
+
+ public void testOneRequiredOneProvidedMatch() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name", String.class);
+ RequiredInterface required = new DefaultRequiredInterface("name", String.class);
+ assertTrue(required.implementedBy(provided));
+ }
+
+ public void testOneRequiredOneProvidedMatchSubClass() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name", Integer.class);
+ RequiredInterface required = new DefaultRequiredInterface("name", Number.class);
+ assertTrue(required.implementedBy(provided));
+ }
+
+ public void testOneRequiredOneProvidedNoMatch() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name", String.class);
+ RequiredInterface required = new DefaultRequiredInterface("name", Number.class);
+ assertFalse(required.implementedBy(provided));
+ }
+
+ public void testOneRequiredMultipleProvidedMatch() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name",
+ new Class[] { String.class, Integer.class} );
+ RequiredInterface required = new DefaultRequiredInterface("name", String.class);
+ assertTrue(required.implementedBy(provided));
+ }
+
+ private static class MyMultiple implements Runnable, Serializable {
+ @Override
+ public void run() {
+ // Empty
+ }
+ }
+
+ public void testMultipleRequiredOneProvidedMatch() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name",
+ MyMultiple.class );
+ RequiredInterface required = new DefaultRequiredInterface("name",
+ new Class[] {Runnable.class, Serializable.class} );
+ assertTrue(required.implementedBy(provided));
+ }
+
+ public void testMultipleRequiredOneProvidedNoMatch() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name",
+ MyMultiple.class );
+ RequiredInterface required = new DefaultRequiredInterface("name",
+ new Class[] { String.class, Runnable.class} );
+ assertFalse(required.implementedBy(provided));
+ }
+
+ public void testMultipleRequiredMultipleProvidedMatch() {
+ ProvidedInterface provided = new DefaultProvidedInterface("name",
+ new Class[] { Runnable.class, Serializable.class, String.class} );
+ RequiredInterface required = new DefaultRequiredInterface("name",
+ new Class[] {Runnable.class, Serializable.class} );
+ assertTrue(required.implementedBy(provided));
+ }
+
+ public void testPrimitiveAndWrapperType() {
+ RequiredInterface req1 = new DefaultRequiredInterface("req1", int.class);
+ RequiredInterface req2 = new DefaultRequiredInterface("req1", Integer.class);
+ ProvidedInterface prov1 = new DefaultProvidedInterface("prov1", int.class);
+ ProvidedInterface prov2 = new DefaultProvidedInterface("prov2", Integer.class);
+ assertTrue(req1.implementedBy(prov1));
+ assertTrue(req2.implementedBy(prov1));
+ assertTrue(req1.implementedBy(prov2));
+ assertTrue(req2.implementedBy(prov2));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import junit.framework.TestCase;
+
+public class DefaultRequiredInterfaceTest extends TestCase {
+
+ public void testEquals() {
+ assertFalse(new DefaultRequiredInterface("a", String.class)
+ .equals(new DefaultRequiredInterface("a", String.class)));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import junit.framework.TestCase;
+
+public class DefaultScopeTest extends TestCase {
+
+ public void testLookup() {
+ ProvidedInterface provider = new DefaultProvidedInterface("x", Integer.class);
+ Scope scope = new DefaultScope(new ProvidedInterface[0]);
+
+ scope.publishInterface(provider, 100);
+ assertEquals(100, scope.getInterfaceImplementation(provider, Integer.class).intValue());
+ }
+
+ public void testNestedLookup() {
+ ProvidedInterface provider1 = new DefaultProvidedInterface("x", Integer.class);
+ Scope parent = new DefaultScope(new ProvidedInterface[0]);
+
+ parent.publishInterface(provider1, 100);
+
+ ProvidedInterface provider2 = new DefaultProvidedInterface("y", String.class);
+ Scope child = new DefaultScope(new ProvidedInterface[0], parent);
+
+ child.publishInterface(provider2, "hallo");
+
+ assertFalse(provider1.equals(provider2));
+
+ assertEquals(100, child.getInterfaceImplementation(provider1, Integer.class).intValue());
+ assertEquals("hallo", child.getInterfaceImplementation(provider2, String.class));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import javax.sql.DataSource;
+
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.test.EventTracker;
+
+public class Environment extends AbstractComponent<Object> {
+
+ private static final ProvidedInterface[] provided(String aPrefix) {
+ return new ProvidedInterface[] {
+ new DefaultProvidedInterface(aPrefix + "datasource", String.class),
+ new DefaultProvidedInterface(aPrefix + "integer", Integer.class) };
+ }
+
+ private static int COUNT = 0;
+
+ private EventTracker<String> _tracker;
+ private double _random;
+ private int _integer;
+
+ public Environment() {
+ this("environment");
+ }
+
+ public Environment(String aName) {
+ this(aName, "");
+ }
+
+ public Environment(String aName, String aPrefix) {
+ super(aName, provided(aPrefix), new RequiredInterface[0]);
+ _random = Math.random();
+ _integer = COUNT++;
+ }
+
+
+
+ public Environment(EventTracker aTracker) {
+ this();
+ _tracker = aTracker;
+ }
+
+ public Integer getInteger() {
+ return _integer;
+ }
+
+ public String getString() {
+ return getName() + ".hello";
+ }
+
+ @Override
+ protected Object doStart(Scope aScope) {
+ addInterface(getProvidedInterfaces().get(0), getString(), aScope);
+ addInterface(getProvidedInterfaces().get(1), getInteger(), aScope);
+ track("start." + getName());
+ return _random;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ track("stop." + getName());
+ if (_random != (Double) aRuntime) {
+ throw new IllegalArgumentException("Wrong runtime: expected "
+ + _random + " but got " + aRuntime);
+ }
+ }
+
+ private void track(String aString) {
+ if (_tracker == null) {
+ return;
+ }
+ _tracker.eventOccurred(aString);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import javax.sql.DataSource;
+
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.test.EventTracker;
+
+public class IntegerComponent extends AbstractComponent<Object> {
+
+ private static final ProvidedInterface[] provided(String aPrefix) {
+ return new ProvidedInterface[] {
+ new DefaultProvidedInterface(aPrefix + "integer", Integer.class) };
+ }
+
+ private EventTracker<String> _tracker;
+ private double _random;
+
+ public IntegerComponent() {
+ this("environment");
+ }
+
+ public IntegerComponent(String aName) {
+ this(aName, "");
+ }
+
+ public IntegerComponent(String aName, String aPrefix) {
+ super(aName, provided(aPrefix), new RequiredInterface[0]);
+ _random = Math.random();
+ }
+
+
+
+ public IntegerComponent(EventTracker aTracker) {
+ this();
+ _tracker = aTracker;
+ }
+
+ public Integer getInteger() {
+ return 2;
+ }
+
+ @Override
+ protected Object doStart(Scope aScope) {
+ addInterface(getProvidedInterfaces().get(1), getInteger(), aScope);
+ track("start." + getName());
+ return _random;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ track("stop." + getName());
+ if (_random != (Double) aRuntime) {
+ throw new IllegalArgumentException("Wrong runtime: expected "
+ + _random + " but got " + aRuntime);
+ }
+ }
+
+ private void track(String aString) {
+ if (_tracker == null) {
+ return;
+ }
+ _tracker.eventOccurred(aString);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import java.util.Comparator;
+
+public class RequiredInterfaceComparator implements
+ Comparator<RequiredInterface> {
+
+ @Override
+ public int compare(RequiredInterface aO1, RequiredInterface aO2) {
+ return aO1.getName().compareTo(aO2.getName());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.core;
+
+import javax.sql.DataSource;
+
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.test.EventTracker;
+
+public class StringComponent extends AbstractComponent<Object> {
+
+ private static final ProvidedInterface[] provided(String aPrefix) {
+ return new ProvidedInterface[] {
+ new DefaultProvidedInterface(aPrefix + "datasource", String.class) };
+ }
+
+ private EventTracker<String> _tracker;
+ private double _random;
+
+ public StringComponent() {
+ this("environment");
+ }
+
+ public StringComponent(String aName) {
+ this(aName, "");
+ }
+
+ public StringComponent(String aName, String aPrefix) {
+ super(aName, provided(aPrefix), new RequiredInterface[0]);
+ _random = Math.random();
+ }
+
+
+
+ public StringComponent(EventTracker aTracker) {
+ this();
+ _tracker = aTracker;
+ }
+
+ public Integer getInteger() {
+ return 2;
+ }
+
+ public String getString() {
+ return getName() + ".hello";
+ }
+
+ @Override
+ protected Object doStart(Scope aScope) {
+ addInterface(getProvidedInterfaces().get(0), getString(), aScope);
+ track("start." + getName());
+ return _random;
+ }
+
+ @Override
+ protected void doStop(Object aRuntime) {
+ track("stop." + getName());
+ if (_random != (Double) aRuntime) {
+ throw new IllegalArgumentException("Wrong runtime: expected "
+ + _random + " but got " + aRuntime);
+ }
+ }
+
+ private void track(String aString) {
+ if (_tracker == null) {
+ return;
+ }
+ _tracker.eventOccurred(aString);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+import junit.framework.TestCase;
+
+import org.wamblee.system.container.Application;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.Environment;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.graph.component.ProvidedInterfaceNode;
+import org.wamblee.system.graph.component.RequiredInterfaceNode;
+
+public class CompositeEdgeFilterTest extends TestCase {
+ private Application _app = new Application();
+ private Environment _env = new Environment();
+
+ private Edge createEdge(Component aClient, RequiredInterface aRequired,
+ Component aServer, ProvidedInterface aProvided) {
+ Node from = new RequiredInterfaceNode(aClient, aRequired);
+ Node to = new ProvidedInterfaceNode(aServer, aProvided);
+ return new DefaultEdge(from, to);
+ }
+
+ public void testEmpty() {
+ EdgeFilter restriction = new CompositeEdgeFilter();
+ assertFalse(restriction.isViolated(createEdge(_app, _app.getRequiredInterfaces().get(0),
+ _env, _env.getProvidedInterfaces().get(0))));
+ }
+
+ private void configureRestriction(EdgeFilter base, boolean aResult) {
+ stub(base.isViolated((Edge)anyObject())).toReturn(aResult);
+ }
+
+ public void testOneRestriction() {
+ EdgeFilter base = mock(EdgeFilter.class);
+ CompositeEdgeFilter composite = new CompositeEdgeFilter();
+ composite.add(base);
+
+ // First let the base return false and verify the result.
+
+ configureRestriction(base, false);
+
+ assertFalse(composite.isViolated(createEdge(_app, _app.getRequiredInterfaces().get(0),
+ _env, _env.getProvidedInterfaces().get(0))));
+
+ // Second let the base return true and verify the result.
+ configureRestriction(base, true);
+
+ assertTrue(composite.isViolated(createEdge(_app, _app.getRequiredInterfaces().get(0),
+ _env, _env.getProvidedInterfaces().get(0))));
+ }
+
+
+
+ public void testTwoRestrictions() {
+ EdgeFilter base1 = mock(EdgeFilter.class);
+ CompositeEdgeFilter composite = new CompositeEdgeFilter();
+ composite.add(base1);
+ EdgeFilter base2 = mock(EdgeFilter.class);
+ composite.add(base2);
+
+ // 1. base1 not violated and base 2 not violated -> not violated.
+
+ configureRestriction(base1, false);
+ configureRestriction(base2, false);
+ assertFalse(composite.isViolated(createEdge(_app, _app.getRequiredInterfaces().get(0),
+ _env, _env.getProvidedInterfaces().get(0))));
+
+ // 2. base 1 not violated but base 2 violated -> violated
+ configureRestriction(base1, false);
+ configureRestriction(base2, true);
+
+ assertTrue(composite.isViolated(createEdge(_app, _app.getRequiredInterfaces().get(0),
+ _env, _env.getProvidedInterfaces().get(0))));
+
+ // 3. base 1 violated -> violated and base 2 not called.
+ configureRestriction(base1, true);
+ // base 2 should not be called.
+
+ assertTrue(composite.isViolated(createEdge(_app, _app.getRequiredInterfaces().get(0),
+ _env, _env.getProvidedInterfaces().get(0))));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
+import org.wamblee.test.AssertionUtils;
+
+
+import junit.framework.TestCase;
+
+public class GraphTest extends TestCase {
+
+ public void testNodeMgt() {
+ final Graph graph = new Graph();
+ assertTrue(graph.getNodes().isEmpty());
+ assertTrue(graph.getEdges().isEmpty());
+
+ final Node x = new DefaultNode("x");
+ graph.addNode(x);
+ assertEquals(Arrays.asList(new Node[] { x }), graph.getNodes());
+ assertSame(x, graph.findNode("x"));
+ assertNull(graph.findNode("y"));
+
+ assertTrue(graph.removeNode(x));
+ assertTrue(graph.getNodes().isEmpty());
+
+ // Empty node set.
+ assertFalse(graph.removeNode(x));
+
+ Node y = new DefaultNode("y");
+ graph.addNodes(Arrays.asList(new Node[] { x, y} ));
+ assertEquals(2, graph.getNodes().size());
+
+ // duplicate node
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ graph.addNode(new DefaultNode("x"));
+ }
+ }, IllegalArgumentException.class);
+ }
+
+ public void testEdgeMgt() {
+ final Graph graph = new Graph();
+ final Node x = new DefaultNode("x");
+ final Node y = new DefaultNode("y");
+ graph.addNode(x);
+ graph.addNode(y);
+ final Edge e = new DefaultEdge(x, y);
+ graph.addEdge(e);
+ assertEquals(Arrays.asList(new Edge[] { e }), graph.getEdges());
+
+ // duplicate edge.
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ graph.addEdge(e);
+ }
+ }, IllegalArgumentException.class);
+
+ // Remove node when edge is still present
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ graph.removeNode(x);
+ }
+ }, IllegalArgumentException.class);
+
+
+ Node a = new DefaultNode("a");
+ graph.addNode(a);
+ graph.addEdges(Arrays.asList(new Edge[] { new DefaultEdge(x, a), new DefaultEdge(y, a) }));
+ assertEquals(3, graph.getEdges().size());
+ }
+
+ public void testExtend() {
+ Graph graph = new Graph();
+ graph.addNode(new MyNode("x", new String[] { "a", "b"}));
+ graph.addNode(new MyNode("y", new String[] { "b", "c"}));
+ graph.addNode(new MyNode("z", new String[] { "a", "c"}));
+ graph.extend(new MyEdgeFactory());
+
+ List<Edge> edges = graph.getEdges();
+ assertEquals(12, edges.size()); // 2 outgoing and 2 incoming nodes.
+ }
+
+ public void testApplyFilter() {
+ Graph graph = new Graph();
+ graph.addNode(new DefaultNode("x"));
+ graph.addNode(new DefaultNode("y"));
+ graph.addNode(new DefaultNode("z"));
+ graph.addEdge(new DefaultEdge(graph.findNode("x"), graph.findNode("y")));
+ graph.addEdge(new DefaultEdge(graph.findNode("y"), graph.findNode("z")));
+ graph.addEdge(new DefaultEdge(graph.findNode("z"), graph.findNode("x")));
+
+ assertEquals(3, graph.getEdges().size());
+
+ graph.applyFilter(new EdgeFilter() {
+ @Override
+ public boolean isViolated(Edge aEdge) {
+ if (aEdge.getFrom().getName().equals("x")) {
+ return false;
+ }
+ return true;
+ }
+ });
+
+ assertEquals(1, graph.getEdges().size());
+ assertEquals("x", graph.getEdges().get(0).getFrom().getName());
+
+ }
+
+ public void testFindIncomingOutgoing() {
+ Graph graph = new Graph();
+ Node x = new DefaultNode("x");
+ Node y = new DefaultNode("y");
+ graph.addNode(x);
+ graph.addNode(y);
+ Edge e = new DefaultEdge(x,y);
+ graph.addEdge(e);
+
+ List<Edge> incoming = graph.findIncoming(x);
+ assertTrue(incoming.isEmpty());
+ List<Edge> outgoing = graph.findOutgoing(x);
+ assertEquals(1, outgoing.size());
+ assertSame(e, outgoing.get(0));
+
+ incoming = graph.findIncoming(y);
+ assertEquals(1, incoming.size());
+ assertSame(e, incoming.get(0));
+
+ outgoing = graph.findOutgoing(y);
+ assertTrue(outgoing.isEmpty());
+ }
+
+ public void testAccept() {
+ Graph graph = new Graph();
+ Node x = new DefaultNode("x");
+ Node y = new DefaultNode("y");
+ Edge e = new DefaultEdge(x, y);
+ graph.addNode(x);
+ graph.addNode(y);
+ graph.addEdge(e);
+ Visitor visitor = mock(Visitor.class);
+
+ graph.accept(visitor);
+ verify(visitor).visitNode(x);
+ verify(visitor).visitNode(y);
+ verify(visitor).visitEdge(e);
+
+ verifyNoMoreInteractions(visitor);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MyEdgeFactory implements EdgeFactory<MyNode> {
+
+ public MyEdgeFactory() {
+ // Empty.
+ }
+
+ @Override
+ public List<Edge> create(MyNode aFrom, MyNode aTo) {
+ List<Edge> result = new ArrayList<Edge>();
+ for (String fromPort: aFrom.getPorts()) {
+ for (String toPort: aTo.getPorts()) {
+ if ( fromPort.equals(toPort)) {
+ result.add(new DefaultEdge(aFrom, aTo));
+ }
+ }
+ }
+
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph;
+
+public class MyNode extends DefaultNode {
+
+ private String[] _ports;
+
+ public MyNode(String aName, String[] aPorts) {
+ super(aName);
+ _ports = aPorts;
+ }
+
+ public String[] getPorts() {
+ return _ports;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import junit.framework.TestCase;
+
+import org.wamblee.system.container.Container;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.Environment;
+import org.wamblee.system.graph.DefaultEdge;
+import org.wamblee.system.graph.DefaultNode;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFilter;
+
+public class ConnectExternalProvidedProvidedEdgeFilterTest extends TestCase {
+
+ private Container _container;
+ private Component<Object> _internal;
+ private String _externalInterfaceName;
+ private String _internalComponentName;
+ private String _internalInterfaceName;
+ private Edge _edge;
+
+ @Override
+ protected void setUp() throws Exception {
+ _container = new Container("container")
+ .addProvidedInterface(new DefaultProvidedInterface("string",
+ String.class));
+ _internal = new Environment("env1");
+
+ _externalInterfaceName = _container.getProvidedInterfaces().get(0)
+ .getName();
+ _internalComponentName = _internal.getName();
+ _internalInterfaceName = _internal.getProvidedInterfaces().get(0).getName();
+
+ _edge = new DefaultEdge(new ExternalProvidedInterfaceNode(_container,
+ _container.getProvidedInterfaces().get(0)),
+ new ProvidedInterfaceNode(_internal, _internal
+ .getProvidedInterfaces().get(0)));
+ }
+
+ public void testWrongExternal() {
+ EdgeFilter filter = new ConnectExternalProvidedProvidedFilter(
+ _externalInterfaceName + "x", _internalComponentName,
+ _internalInterfaceName);
+ assertFalse(filter.isViolated(_edge));
+ }
+
+ public void testRightExternalWrongComponent() {
+ EdgeFilter filter = new ConnectExternalProvidedProvidedFilter(
+ _externalInterfaceName, _internalComponentName + "x",
+ _internalInterfaceName);
+ assertTrue(filter.isViolated(_edge));
+ }
+
+ public void testRightExternalWrongInternal() {
+ EdgeFilter filter = new ConnectExternalProvidedProvidedFilter(
+ _externalInterfaceName, _internalComponentName,
+ _internalInterfaceName + "x");
+ assertTrue(filter.isViolated(_edge));
+ }
+
+ public void testEverythingRight() {
+ EdgeFilter filter = new ConnectExternalProvidedProvidedFilter(
+ _externalInterfaceName, _internalComponentName,
+ _internalInterfaceName);
+ assertFalse(filter.isViolated(_edge));
+ }
+
+
+ public void testWrongEdgeType() {
+ EdgeFilter filter = new ConnectExternalProvidedProvidedFilter(
+ _externalInterfaceName, _internalComponentName,
+ _internalInterfaceName);
+ DefaultEdge edge = new DefaultEdge(new DefaultNode("x"),
+ new DefaultNode("y"));
+ assertFalse(filter.isViolated(edge));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import junit.framework.TestCase;
+
+import org.wamblee.system.container.Application;
+import org.wamblee.system.container.Container;
+import org.wamblee.system.core.Component;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.graph.DefaultEdge;
+import org.wamblee.system.graph.DefaultNode;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFilter;
+import org.wamblee.system.graph.Node;
+
+public class ConnectRequiredExternallyRequiredEdgeFilterTest extends TestCase {
+
+ private Component<?> _comp;
+ private Container _container;
+ private Edge _edge;
+
+ @Override
+ protected void setUp() throws Exception {
+ _comp = new Application();
+ _container = new Container("container")
+ .addRequiredInterface(new DefaultRequiredInterface("x",
+ String.class));
+ Node req = new RequiredInterfaceNode(_comp, _comp
+ .getRequiredInterfaces().get(0));
+ Node external = new ExternalRequiredInterfaceNode(_container,
+ _container.getRequiredInterfaces().get(0));
+ _edge = new DefaultEdge(req, external);
+ }
+
+ public void testRightComponentRightInterface() {
+ EdgeFilter filter = new ConnectRequiredExternallyRequiredEdgeFilter(
+ _comp.getName(), _comp.getRequiredInterfaces().get(0).getName(),
+ _container.getRequiredInterfaces().get(0).getName());
+ assertFalse(filter.isViolated(_edge));
+ }
+
+ public void testWrongInterface() {
+ EdgeFilter filter = new ConnectRequiredExternallyRequiredEdgeFilter(
+ _comp.getName(), _comp.getRequiredInterfaces().get(0).getName()
+ + "xx", _container.getRequiredInterfaces().get(0).getName());
+ assertFalse(filter.isViolated(_edge));
+ }
+
+ public void testWrongComponent() {
+ EdgeFilter filter = new ConnectRequiredExternallyRequiredEdgeFilter(
+ _comp.getName() + "xx", _comp.getRequiredInterfaces().get(0)
+ .getName(), _container.getRequiredInterfaces().get(0)
+ .getName());
+ assertFalse(filter.isViolated(_edge));
+ }
+
+ public void testWrongExternalInterface() {
+ EdgeFilter filter = new ConnectRequiredExternallyRequiredEdgeFilter(
+ _comp.getName(), _comp.getRequiredInterfaces().get(0).getName(),
+ _container.getRequiredInterfaces().get(0).getName() + "xx");
+ assertTrue(filter.isViolated(_edge));
+ }
+
+ public void testWrongEdgeType() {
+ DefaultEdge edge = new DefaultEdge(new DefaultNode("x"),
+ new DefaultNode("y"));
+ EdgeFilter filter = new ConnectRequiredExternallyRequiredEdgeFilter(
+ _comp.getName(), _comp.getRequiredInterfaces().get(0).getName(),
+ _container.getRequiredInterfaces().get(0).getName());
+ assertFalse(filter.isViolated(edge));
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.graph.component;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.wamblee.system.container.Application;
+import org.wamblee.system.core.Environment;
+import org.wamblee.system.graph.DefaultEdge;
+import org.wamblee.system.graph.Edge;
+import org.wamblee.system.graph.EdgeFilter;
+import org.wamblee.system.graph.Node;
+import org.wamblee.test.AssertionUtils;
+
+import junit.framework.TestCase;
+
+public class ConnectRequiredProvidedEdgeFilterTest extends TestCase {
+
+ private Application _app1 = new Application("app1", "pf1.");
+ private Application _app2 = new Application("app2", "pf2.");
+
+ private Environment _env1 = new Environment("env1", "pf3.");
+ private Environment _env2 = new Environment("env2", "pf4.");
+
+
+ private void compare(Boolean[] aExpected, EdgeFilter aRestriction) {
+ List<Boolean> result = new ArrayList<Boolean>();
+
+
+
+ // order will be:
+ // env1, app1
+ // env1, app2
+ // env2, app1
+ // env2, app2
+ for (Environment env: new Environment[] { _env1, _env2} ) {
+ for (Application app: new Application[] { _app1, _app2} ) {
+ Node from = new RequiredInterfaceNode(
+ app, app.getRequiredInterfaces().get(0));
+ Node to = new ProvidedInterfaceNode(
+ env, env.getProvidedInterfaces().get(0));
+ Edge edge = new DefaultEdge(from, to);
+ result.add(aRestriction.isViolated(edge));
+ }
+ }
+
+
+ assertEquals(Arrays.asList(aExpected), result);
+ }
+
+ public void testNoRestriction() {
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ EdgeFilter restriction = new ConnectRequiredProvidedEdgeFilter(null, null, null, null);
+
+ }
+ }, IllegalArgumentException.class);
+ AssertionUtils.assertException(new AssertionUtils.ErroneousCode() {
+ @Override
+ public void run() throws Exception {
+ EdgeFilter restriction = new ConnectRequiredProvidedEdgeFilter(null, null, "x", "y");
+ }
+ }, IllegalArgumentException.class);
+ }
+
+ public void testClientServer() {
+ EdgeFilter restriction = new ConnectRequiredProvidedEdgeFilter("app1", null, "env1", null);
+ compare(new Boolean[] { false, false, true, false}, restriction);
+ }
+
+ public void testNoConnectionsAtAll() {
+ EdgeFilter restriction = new ConnectRequiredProvidedEdgeFilter("app1", null, null, null);
+ compare(new Boolean[] { true, false, true, false}, restriction);
+ }
+
+ public void testExplicitConfig() {
+ _app1 = new Application("app1");
+ _app2 = new Application("app2");
+ _env1 = new Environment("env1");
+ _env2 = new Environment("env2");
+
+ EdgeFilter restriction = new ConnectRequiredProvidedEdgeFilter(
+ "app2", "string", "env1", "datasource");
+ compare(new Boolean[] { false, false, false, true}, restriction);
+
+ }
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system</artifactId>
+ <packaging>pom</packaging>
+ <name>/system</name>
+ <url>http://wamblee.org</url>
+ <modules>
+ <module>general</module>
+ <module>spring</module>
+ </modules>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system-spring</artifactId>
+ <packaging>jar</packaging>
+ <version>0.2</version>
+ <name>/system/spring</name>
+ <url>http://wamblee.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-system-general</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-spring</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-spring</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-hibernate-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class ConfiguredProperties extends Properties {
+
+ public ConfiguredProperties(String aProps) throws IOException {
+ InputStream is = new ByteArrayInputStream(aProps.getBytes());
+ load(is);
+ is.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.util.Properties;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
+
+/**
+ * Bean that provides postprocessing of the bean factory based on
+ * a given set of properties.
+ *
+ * @author Erik Brakkee
+ */
+class PropertySetter extends PropertyPlaceholderConfigurer {
+
+ /**
+ * Constructs the property setter.
+ * @param aProps Properties.
+ */
+ public PropertySetter(Properties aProps) {
+ String propFile = createPropertyFile(aProps);
+ setLocation(new StringResource(propFile));
+ }
+
+ public static String createPropertyFile(Properties aProps) {
+ StringBuffer buf = new StringBuffer();
+ for (Object key: aProps.keySet()) {
+ buf.append(key);
+ buf.append("=");
+ buf.append(aProps.get(key));
+ buf.append("\n");
+ }
+ return buf.toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.util.List;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.SystemAssemblyException;
+
+/**
+ * Bean which adds a service required by the spring component to
+ * the application context so that other spring beans can use it.
+ *
+ * @author Erik Brakkee
+ */
+class RequiredServiceBean implements FactoryBean {
+
+ private RequiredInterface _required;
+
+ /**
+ * Constructs the bean.
+ * @param aId Id of the bean in the service registry.
+ */
+ public RequiredServiceBean(String aId) {
+ List<RequiredInterface> required = SpringComponent.THIS.get().getRequiredInterfaces();
+ for ( RequiredInterface intf: required) {
+ if ( intf.getName().equals(aId)) {
+ _required = intf;
+ return;
+ }
+ }
+ throw new SystemAssemblyException("Cannot resolve required component '" + aId + "'");
+ }
+
+ @Override
+ public Object getObject() throws Exception {
+ return SpringComponent.SCOPE.get().getInterfaceImplementation(
+ _required.getProvider(), Object.class);
+ }
+
+ @Override
+ public Class getObjectType() {
+ return null;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConstructorArgumentValues;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultScope;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.core.SystemAssemblyException;
+
+/**
+ * Represents a system configured based on spring. The spring config files that
+ * are configured should not contain any PropertyPlaceholderConfigurer objects.
+ *
+ * @author Erik Brakkee
+ */
+public class SpringComponent extends AbstractComponent<Scope> {
+
+ private static final String CONTEXT_KEY = "context";
+
+ static final ThreadLocal<SpringComponent> THIS = new ThreadLocal<SpringComponent>();
+ static final ThreadLocal<Scope> SCOPE = new ThreadLocal<Scope>();
+
+ private Properties _properties;
+ private String[] _configFiles;
+ private Map<String, ProvidedInterface> _provided;
+ private Map<RequiredInterface, String> _required;
+ private Map<String, Properties> _propertyObjects;
+
+ /**
+ * Constructs a spring system.
+ *
+ * @param aName
+ * Name of the system.
+ * @param aConfigFil
+ * Spring config files to read.
+ * @param aProvided
+ * Map of bean name to service descriptor describing the bean
+ * names that the spring config files use for each required
+ * service.
+ * @param aRequired
+ * Map of bean name to service descriptor describing the bean
+ * names that the spring config files use for each required
+ * service.
+ */
+ public SpringComponent(String aName, String[] aConfigFiles,
+ Map<String, ProvidedInterface> aProvided,
+ Map<RequiredInterface, String> aRequired) {
+ super(aName, aProvided.values().toArray(new ProvidedInterface[0]),
+ aRequired.keySet().toArray(new RequiredInterface[0]));
+ _properties = new Properties();
+ _configFiles = aConfigFiles;
+ _provided = aProvided;
+ _required = aRequired;
+ _propertyObjects = new HashMap<String, Properties>();
+
+ }
+
+ /**
+ * Must be called to make a property available in the application context.
+ *
+ * @param aKey
+ * Property key.
+ * @param aValue
+ * Property value.
+ */
+ public void setProperty(String aKey, String aValue) {
+ _properties.put(aKey, aValue);
+ }
+
+ public void addProperties(Properties aProperties) {
+ for (Object key : aProperties.keySet()) {
+ setProperty((String) key, aProperties.getProperty((String) key));
+ }
+ }
+
+ public void addProperties(String aBeanname, Properties aProperties) {
+ _propertyObjects.put(aBeanname, aProperties);
+ }
+
+ public Properties getProperties(String aBeanname) {
+ return _propertyObjects.get(aBeanname);
+ }
+
+ @Override
+ protected Scope doStart(Scope aExternalScope) {
+
+ SpringComponent old = THIS.get();
+ Scope oldScope = SCOPE.get();
+ THIS.set(this);
+ Scope scope = new DefaultScope(getProvidedInterfaces().toArray(new ProvidedInterface[0]), aExternalScope);
+ SCOPE.set(scope);
+ try {
+ GenericApplicationContext parentContext = new GenericApplicationContext();
+
+ registerRequiredServices(parentContext);
+ registerPropertyObjects(parentContext);
+
+ parentContext.refresh();
+
+ System.out.println("Parent context " + parentContext);
+
+ AbstractApplicationContext context = parseConfigFiles(parentContext);
+
+ context
+ .addBeanFactoryPostProcessor(new PropertySetter(_properties));
+ context.refresh();
+
+ exposeProvidedServices(context, aExternalScope);
+
+ scope.put(CONTEXT_KEY, context);
+ return scope;
+ } catch (Exception e) {
+ throw new SystemAssemblyException(
+ "Failed to assemble spring system " + getName(), e);
+ } finally {
+ THIS.set(old);
+ SCOPE.set(oldScope);
+ }
+ }
+
+ private void exposeProvidedServices(AbstractApplicationContext aContext, Scope aScope) {
+ // Call addService for each provided service.
+
+ for (String name : _provided.keySet()) {
+ Object svc = aContext.getBean(name);
+ if (svc == null) {
+ throw new IllegalArgumentException(getQualifiedName() + ": service '"
+ + name + "' is null");
+ }
+ addInterface(_provided.get(name), svc, aScope);
+ System.out.println("addService " + _provided.get(name) + " " + svc);
+ }
+ }
+
+ private AbstractApplicationContext parseConfigFiles(GenericApplicationContext aParentContext) {
+ // Parse spring config files
+
+ return new ClassPathXmlApplicationContext((String[]) _configFiles,
+ false, aParentContext);
+ }
+
+ private void registerRequiredServices(GenericApplicationContext aParentContext) {
+ // Register required services in a parent context
+ for (RequiredInterface required: getRequiredInterfaces()) {
+ String beanName = _required.get(required);
+ if ( beanName != null && beanName.length() > 0) {
+ ConstructorArgumentValues cargs = new ConstructorArgumentValues();
+ cargs.addGenericArgumentValue(required.getName());
+ BeanDefinition definition = new RootBeanDefinition(
+ RequiredServiceBean.class, cargs,
+ new MutablePropertyValues());
+ aParentContext.registerBeanDefinition(beanName, definition);
+ } else {
+ // The required interface is not required by the spring config but by the sub-class directly.
+ }
+ }
+ }
+
+ private void registerPropertyObjects(GenericApplicationContext aParentContext) {
+ for (String beanName: _propertyObjects.keySet()) {
+ ConstructorArgumentValues cargs = new ConstructorArgumentValues();
+ cargs.addGenericArgumentValue(PropertySetter.createPropertyFile(_propertyObjects.get(beanName)));
+ BeanDefinition definition = new RootBeanDefinition(
+ ConfiguredProperties.class, cargs,
+ new MutablePropertyValues());
+ aParentContext.registerBeanDefinition(beanName, definition);
+ }
+ }
+
+
+ @Override
+ protected void doStop(Scope aRuntime) {
+ AbstractApplicationContext context = (AbstractApplicationContext)aRuntime.get(CONTEXT_KEY);
+ context.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.springframework.core.io.Resource;
+
+class StringResource implements Resource {
+
+ private String _value;
+
+ public StringResource(String aValue) {
+ _value = aValue;
+ }
+
+ @Override
+ public Resource createRelative(String aRelativePath) throws IOException {
+ throw new IOException("No relative resource possible");
+ }
+
+ @Override
+ public boolean exists() {
+ return false;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Properties of a spring component";
+ }
+
+ @Override
+ public File getFile() throws IOException {
+ throw new IOException();
+ }
+
+ @Override
+ public String getFilename() {
+ return "springcomponent.properties";
+ }
+
+ @Override
+ public URL getURL() throws IOException {
+ throw new IOException();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return false;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(_value.getBytes());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.spring.component;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.wamblee.system.components.ORMappingConfig;
+import org.wamblee.system.components.ORMappingConfig.DatabaseType;
+import org.wamblee.system.core.AbstractComponent;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.SpringComponent;
+
+public class DatasourceComponent extends AbstractComponent<DataSource> {
+
+ private static RequiredInterface PROPS = new DefaultRequiredInterface("dbprops", Properties.class);
+ private static ProvidedInterface DATASOURCE = new DefaultProvidedInterface("datasource",
+ DataSource.class);
+ private static ProvidedInterface ORM_CONFIG = new DefaultProvidedInterface("ormconfig",
+ ORMappingConfig.class);
+
+ public DatasourceComponent(String aName) throws IOException {
+ super(aName);
+
+ addRequiredInterface(PROPS);
+ addProvidedInterface(DATASOURCE);
+ addProvidedInterface(ORM_CONFIG);
+ }
+
+
+ @Override
+ protected DataSource doStart(Scope aScope) {
+ Properties dbProps = aScope.getInterfaceImplementation(
+ PROPS.getProvider(), Properties.class);
+ DriverManagerDataSource ds = new DriverManagerDataSource(
+ dbProps.getProperty("database.url"),
+ dbProps.getProperty("database.username"),
+ dbProps.getProperty("database.password"));
+ addInterface(DATASOURCE, ds, aScope);
+ DatabaseType type = DatabaseType.valueOf(dbProps.getProperty("database.type"));
+
+ ORMappingConfig config = new ORMappingConfig(true, type);
+
+ addInterface(ORM_CONFIG, config, aScope);
+
+ return ds;
+ }
+
+ @Override
+ protected void doStop(DataSource aRuntime) {
+ // Empty.
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.spring.component;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.dialect.DerbyDialect;
+import org.hibernate.dialect.MySQLInnoDBDialect;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.wamblee.persistence.hibernate.HibernateMappingFiles;
+import org.wamblee.system.components.ORMappingConfig;
+import org.wamblee.system.components.ORMappingConfig.DatabaseType;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.spring.SpringComponent;
+
+public class HibernateComponent extends SpringComponent {
+
+ private static final String HIBERNATE_DIALECT_PROP = "hibernate.dialect";
+ private static final String HIBERNATE_SCHEMAUPDATE_PROP = "hibernate.schemaupdate";
+ private static final String HIBERNATE_PROPS_KEY = "hibernateProperties";
+ private static final String HIBERNATE_SPRING_CONFIG = "spring/org.wamblee.system.spring.component.hibernate.xml";
+
+ private final RequiredInterface CONFIG = new DefaultRequiredInterface("config", ORMappingConfig.class);
+
+ public HibernateComponent(String aName) throws IOException {
+ super(aName, new String[] { HIBERNATE_SPRING_CONFIG},
+ createProvided(), createRequired());
+
+ Properties props = new Properties();
+ addProperties(HIBERNATE_PROPS_KEY, props);
+
+ addRequiredInterface(CONFIG);
+ }
+
+ @Override
+ protected Scope doStart(Scope aExternalScope) {
+
+ ORMappingConfig config = aExternalScope.getInterfaceImplementation(CONFIG.getProvider(), ORMappingConfig.class);
+ setProperty(HIBERNATE_SCHEMAUPDATE_PROP, "" + config.isSchemaUpdate());
+
+ DatabaseType db = config.getType();
+ String dialect = db.handleCases(new DatabaseType.Switch<String>() {
+ @Override
+ public String handleMySqlInnoDb() {
+ return MySQLInnoDBDialect.class.getName();
+ }
+ @Override
+ public String handleDerby() {
+ return DerbyDialect.class.getName();
+ }
+ });
+ getHibernateProperties().put(HIBERNATE_DIALECT_PROP, dialect);
+
+
+ return super.doStart(aExternalScope);
+ }
+
+ private Properties getHibernateProperties() {
+ return getProperties(HIBERNATE_PROPS_KEY);
+ }
+
+ private static Map<RequiredInterface, String> createRequired() {
+ Map<RequiredInterface,String> required = new HashMap<RequiredInterface, String>();
+ required.put(new DefaultRequiredInterface("datasource", DataSource.class), "dataSource");
+ required.put(new DefaultRequiredInterface("mappingFiles", HibernateMappingFiles.class),
+ "hibernateMappingFiles");
+ return required;
+ }
+
+ private static Map<String, ProvidedInterface> createProvided() {
+ Map<String,ProvidedInterface> provided = new HashMap<String,ProvidedInterface>();
+
+ provided.put("transactionManager", new DefaultProvidedInterface(
+ "transactionMgr", PlatformTransactionManager.class));
+ provided.put("sessionFactory", new DefaultProvidedInterface(
+ "sessionFactory", SessionFactory.class));
+ provided.put("org.springframework.orm.hibernate3.HibernateTemplate", new DefaultProvidedInterface(
+ "hibernateTemplate", HibernateTemplate.class));
+ return provided;
+ }
+}
--- /dev/null
+
+###################################################################################
+# dialect
+###################################################################################
+#hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect
+
+###################################################################################
+# hibernate cache provider
+###################################################################################
+#hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
+
+###################################################################################
+# query cache
+###################################################################################
+#hibernate.cache.use_query_cache=true
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<!-- This is the Spring configuration to define the database-related stuff for the
+ all persistence tests. -->
+<beans>
+
+ <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
+ <property name="dataSource">
+ <ref bean="dataSource"/>
+ </property>
+ <property name="hibernateProperties">
+ <ref bean="hibernateProperties"/>
+ </property>
+ <property name="schemaUpdate">
+ <value>${hibernate.schemaupdate}</value>
+ </property>
+ <property name="mappingResources"><ref bean="hibernateMappingFiles"/></property>
+ </bean>
+
+ <bean id="transactionManager"
+ class="org.springframework.orm.hibernate3.HibernateTransactionManager">
+ <property name="sessionFactory">
+ <ref local="sessionFactory"/>
+ </property>
+ </bean>
+
+ <!-- Hibernate template used within test code for addition
+ Hibernate-specific stuff -->
+ <bean id="org.springframework.orm.hibernate3.HibernateTemplate"
+ class="org.springframework.orm.hibernate3.HibernateTemplate">
+ <property name="sessionFactory">
+ <ref bean="sessionFactory"/>
+ </property>
+ </bean>
+</beans>
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+public class BlaService {
+ private HelloService _hello;
+
+ public BlaService(HelloService aService) {
+ if ( aService == null ) {
+ throw new IllegalArgumentException("helloService is null");
+ }
+ _hello = aService;
+ }
+
+ public String execute() {
+ return _hello.say();
+ }
+
+ public void stop() {
+ System.out.println("Blaservice stopping");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class ConfiguredPropertiesTest extends TestCase {
+
+ public void testProps() throws IOException {
+ Properties props = new Properties();
+ props.put("x", "y");
+ props.put("a", "b");
+
+ ConfiguredProperties props2 = new ConfiguredProperties(
+ PropertySetter.createPropertyFile(props));
+
+ assertEquals(props.size(), props2.size());
+ assertEquals(props.get("x"), props2.get("x"));
+ assertEquals(props.get("a"), props2.get("a"));
+ }
+}
+
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+public class HelloService {
+
+ private String _msg;
+
+ public HelloService(String aMsg) {
+ SpringComponentTest.EVENT_TRACKER.eventOccurred(aMsg);
+ _msg = aMsg;
+ }
+
+ public String say() {
+ return _msg;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.util.Properties;
+
+public class HelloService2 {
+
+ private Properties _props;
+
+ public HelloService2(Properties aProps) {
+ _props = aProps;
+ }
+
+ public Properties getProperties() {
+ return _props;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.wamblee.io.ClassPathResource;
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.DefaultScope;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+import org.wamblee.system.core.SystemAssemblyException;
+import org.wamblee.test.EventTracker;
+
+public class SpringComponentTest extends TestCase {
+
+ private static final String HELLO_SERVICE_SPRING_XML = "test.org.wamblee.system.spring.xml";
+ private static final String HELLO_SERVICE_SPRING_WITH_REQS_XML = "test.org.wamblee.system.springWithRequirements.xml";
+ private static final String HELLO_SERVICE_SPRING_WITH_PROPERTIES_XML = "test.org.wamblee.system.springWithProperties.xml";
+ private static final String HELLO_SERVICE_SPRING_WITH_PROPERTIES_XML2 = "test.org.wamblee.system.springWithProperties2.xml";
+
+ private static final String PROPERTY_FILE = "test.org.wamblee.system.spring.properties";
+
+ public static EventTracker<String> EVENT_TRACKER;
+
+ private Scope _externalScope;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ EVENT_TRACKER = new EventTracker<String>();
+ _externalScope = new DefaultScope(new ProvidedInterface[0]);
+ }
+
+ public void testBlackboxSystem() {
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_XML },
+ new HashMap<String, ProvidedInterface>(),
+ new HashMap<RequiredInterface, String>());
+
+ Scope runtime = system.start(_externalScope);
+ assertEquals(0, _externalScope.getProvidedInterfaces().size());
+
+ system.stop(runtime);
+ }
+
+ public void testOneProvidedService() {
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put("helloService", new DefaultProvidedInterface("hello",
+ HelloService.class));
+
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_XML }, provided,
+ new HashMap<RequiredInterface, String>());
+ Scope runtime = system.start(_externalScope);
+ List<ProvidedInterface> services = runtime.getProvidedInterfaces();
+
+ assertEquals(1, services.size());
+ Object service = runtime.getInterfaceImplementation(services.get(0),
+ Object.class);
+ assertTrue(service instanceof HelloService);
+
+ // BUG; Provided services should be made available in the external
+ // scope.
+ Object service2 = _externalScope.getInterfaceImplementation(provided
+ .get("helloService"), Object.class);
+ assertSame(service, service2);
+
+ assertEquals("Hello world!", ((HelloService) service).say());
+ system.stop(runtime);
+ }
+
+ public void testWithProperties() throws IOException {
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put("helloService", new DefaultProvidedInterface("hello",
+ HelloService.class));
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_WITH_PROPERTIES_XML },
+ provided, new HashMap<RequiredInterface, String>());
+ Properties props = new Properties();
+ props.load(new ClassPathResource(PROPERTY_FILE).getInputStream());
+ system.addProperties(props);
+
+ Scope scope = system.start(_externalScope);
+ // BUG: Hello service was constructed multiple times. Once with the
+ // unprocessed property
+ // and another time with the processed property.
+ assertEquals(1, EVENT_TRACKER.getEventCount());
+ List<ProvidedInterface> services = scope.getProvidedInterfaces();
+ assertEquals("Property Value", scope.getInterfaceImplementation(
+ services.get(0), HelloService.class).say());
+ }
+
+ public void testWithPropertiesAsBean() throws IOException {
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put("helloService", new DefaultProvidedInterface("hello",
+ HelloService2.class));
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_WITH_PROPERTIES_XML2 },
+ provided, new HashMap<RequiredInterface, String>());
+ Properties props = new Properties();
+ props.load(new ClassPathResource(PROPERTY_FILE).getInputStream());
+ system.addProperties("properties", props);
+
+ Scope scope = system.start(_externalScope);
+
+ List<ProvidedInterface> services = scope.getProvidedInterfaces();
+
+ Properties props2 = scope.getInterfaceImplementation(services.get(0),
+ HelloService2.class).getProperties();
+ assertEquals(props, props2);
+ }
+
+ public void testWithMissingRequirement() {
+ try {
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML },
+ new HashMap<String, ProvidedInterface>(),
+ new HashMap<RequiredInterface, String>());
+ system.start(_externalScope);
+ } catch (SystemAssemblyException e) {
+ // e.printStackTrace();
+ return;
+ }
+ fail();
+ }
+
+ public void testWithRequirement() {
+ Map<RequiredInterface, String> required = new HashMap<RequiredInterface, String>();
+ required.put(new DefaultRequiredInterface("hello", HelloService.class),
+ "helloService");
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML },
+ new HashMap<String, ProvidedInterface>(), required);
+
+ HelloService helloObject = new HelloService("ladida");
+ ProvidedInterface helloService = new DefaultProvidedInterface("hello",
+ HelloService.class);
+ Scope scope = new DefaultScope(new ProvidedInterface[] { helloService });
+ scope.publishInterface(helloService, helloObject);
+ system.getRequiredInterfaces().get(0).setProvider(helloService);
+
+ Scope runtime = system.start(scope);
+ system.stop(runtime);
+ }
+
+ public void testWithRequirementAndProvidedService() {
+ Map<RequiredInterface, String> required = new HashMap<RequiredInterface, String>();
+ required.put(new DefaultRequiredInterface("hello", HelloService.class),
+ "helloService");
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put("blaService", new DefaultProvidedInterface("bla",
+ BlaService.class));
+
+ SpringComponent system = new SpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML }, provided,
+ required);
+
+ HelloService helloObject = new HelloService("ladida");
+ ProvidedInterface helloService = new DefaultProvidedInterface("hello",
+ HelloService.class);
+ Scope scope = new DefaultScope(new ProvidedInterface[] { helloService });
+ scope.publishInterface(helloService, helloObject);
+ system.getRequiredInterfaces().get(0).setProvider(helloService);
+ Scope runtime = system.start(scope);
+ ProvidedInterface started = runtime.getProvidedInterfaces().get(0);
+
+ Object impl = runtime.getInterfaceImplementation(started,
+ BlaService.class);
+ assertNotNull(impl);
+ assertTrue(impl instanceof BlaService);
+ assertEquals("ladida", ((BlaService) impl).execute());
+ system.stop(runtime);
+ }
+
+ /**
+ * Tests a scenario where a subclass of SpringComponent adds a new provided
+ * interface where the interface is provided by the subclass itself and not
+ * by the spring configs inside.
+ */
+ public void testWithProvidedFromSubClassNotFromConfig() {
+ Map<String, ProvidedInterface> provided = new HashMap<String, ProvidedInterface>();
+ provided.put("helloService", new DefaultProvidedInterface("hello",
+ HelloService.class));
+
+ SubSpringComponent system = new SubSpringComponent("system",
+ new String[] { HELLO_SERVICE_SPRING_XML }, provided,
+ new HashMap<RequiredInterface, String>());
+
+ Scope runtime = system.start(_externalScope);
+ List<ProvidedInterface> services = runtime.getProvidedInterfaces();
+
+ assertEquals(2, services.size());
+ Object service = runtime.getInterfaceImplementation(services.get(0),
+ Object.class);
+ assertTrue(service instanceof HelloService);
+
+ // BUG; Provided services should be made available in the external
+ // scope.
+ Object service2 = _externalScope.getInterfaceImplementation(provided
+ .get("helloService"), Object.class);
+ assertSame(service, service2);
+
+ Object floatsvc = _externalScope.getInterfaceImplementation(system
+ .getProvidedInterfaces().get(1), Object.class);
+ assertTrue(floatsvc instanceof Float);
+ assertTrue((((Float) floatsvc).floatValue() - 100.345f) < 0.00001);
+
+ assertEquals("Hello world!", ((HelloService) service).say());
+ system.stop(runtime);
+ }
+
+ /**
+ * Tests the spring component with an additional requirement from the subclass
+ * which is not required by the spring config files inside.
+ */
+ public void testWithRequirementFromSubClass() {
+ Map<RequiredInterface, String> required = new HashMap<RequiredInterface, String>();
+ required.put(new DefaultRequiredInterface("hello", HelloService.class),
+ "helloService");
+ SpringComponent system = new SubSpringComponent2("system",
+ new String[] { HELLO_SERVICE_SPRING_WITH_REQS_XML },
+ new HashMap<String, ProvidedInterface>(), required);
+
+ HelloService helloObject = new HelloService("ladida");
+ ProvidedInterface helloService = new DefaultProvidedInterface("hello",
+ HelloService.class);
+
+ ProvidedInterface floatService = new DefaultProvidedInterface("float", Float.class);
+
+ Scope scope = new DefaultScope(new ProvidedInterface[] { helloService });
+ scope.publishInterface(helloService, helloObject);
+ scope.publishInterface(floatService, 100.234f);
+ system.getRequiredInterfaces().get(0).setProvider(helloService);
+ system.getRequiredInterfaces().get(1).setProvider(floatService);
+
+ Scope runtime = system.start(scope);
+ system.stop(runtime);
+
+ assertEquals(100.234f, ((Float)runtime.get("floatValue")).floatValue(), 0.0001f);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.util.Map;
+
+
+import org.wamblee.system.core.DefaultProvidedInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+public class SubSpringComponent extends SpringComponent {
+
+ private static ProvidedInterface PROVIDED = new DefaultProvidedInterface("provided", Float.class);
+
+ public SubSpringComponent(String aName, String[] aConfigFiles,
+ Map<String, ProvidedInterface> aProvided,
+ Map<RequiredInterface, String> aRequired) {
+ super(aName, aConfigFiles, aProvided, aRequired);
+ addProvidedInterface(PROVIDED);
+ }
+
+ @Override
+ protected Scope doStart(Scope aExternalScope) {
+
+ Scope scope = super.doStart(aExternalScope);
+ addInterface(PROVIDED, 100.345f, aExternalScope);
+ return scope;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * 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.wamblee.system.spring;
+
+import java.util.Map;
+
+import org.wamblee.system.core.DefaultRequiredInterface;
+import org.wamblee.system.core.ProvidedInterface;
+import org.wamblee.system.core.RequiredInterface;
+import org.wamblee.system.core.Scope;
+
+public class SubSpringComponent2 extends SpringComponent {
+
+ private static RequiredInterface REQUIRED = new DefaultRequiredInterface("required", Float.class);
+
+ public SubSpringComponent2(String aName, String[] aConfigFiles,
+ Map<String, ProvidedInterface> aProvided,
+ Map<RequiredInterface, String> aRequired) {
+ super(aName, aConfigFiles, aProvided, aRequired);
+ addRequiredInterface(REQUIRED);
+ }
+
+ @Override
+ protected Scope doStart(Scope aExternalScope) {
+
+ Scope scope = super.doStart(aExternalScope);
+
+ float value = aExternalScope.getInterfaceImplementation(REQUIRED.getProvider(), Float.class);
+ scope.put("floatValue", value);
+ return scope;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.system.spring.component;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.operation.DatabaseOperation;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionTemplate;
+import org.wamblee.test.spring.TestTransactionCallback;
+import org.wamblee.test.spring.TestTransactionCallbackWithoutResult;
+
+/**
+ * Test support class for database testing. Currently, this still requires the
+ * spring platform transaction manager and hibernate template.
+ */
+public class DatabaseTesterComponent {
+
+ private static final Log LOG = LogFactory
+ .getLog(DatabaseTesterComponent.class);
+
+ /**
+ * Schema pattern.
+ */
+ private static final String SCHEMA_PATTERN = "%";
+
+ /**
+ * Cached spring application context.
+ */
+ private ApplicationContext _context;
+
+ private HibernateTemplate _hibernateTemplate;
+
+ private PlatformTransactionManager _transactionManager;
+
+ private DataSource _dataSource;
+
+ public DatabaseTesterComponent(HibernateTemplate aHibernateTemplate,
+ PlatformTransactionManager aTransactionManager,
+ DataSource aDataSource) {
+ _hibernateTemplate = aHibernateTemplate;
+ _transactionManager = aTransactionManager;
+ _dataSource = aDataSource;
+ }
+
+ /**
+ * @return Hibernate session factory.
+ */
+ protected SessionFactory getSessionFactory() {
+ return _hibernateTemplate.getSessionFactory();
+ }
+
+ /**
+ * Performs common initialization for test cases:
+ * <ul>
+ * <li>Cleaning the database. </li>
+ * </ul>
+ *
+ * @throws Exception
+ */
+ public void setUp() throws Exception {
+ LOG.info("Performing setUp()");
+
+ cleanDatabase();
+ }
+
+ /**
+ * Performs common tear down after execution of a test case. Currenlty this
+ * method does nothing.
+ *
+ * @throws Exception
+ */
+ protected void tearDown() throws Exception {
+ // Empty
+ }
+
+ /**
+ * @return Transaction manager
+ */
+ protected PlatformTransactionManager getTransactionManager() {
+ return _transactionManager;
+ }
+
+ /**
+ * @return Starts a new transaction.
+ */
+ protected TransactionStatus getTransaction() {
+ DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+ def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+ return getTransactionManager().getTransaction(def);
+ }
+
+ /**
+ * Returns the hibernate template for executing hibernate-specific
+ * functionality.
+ *
+ * @return Hibernate template.
+ */
+ public HibernateTemplate getTemplate() {
+ return _hibernateTemplate;
+ }
+
+ /**
+ * Flushes the session. Should be called after some Hibernate work and
+ * before JDBC is used to check results.
+ *
+ */
+ public void flush() {
+ getTemplate().flush();
+ }
+
+ /**
+ * Flushes the session first and then removes all objects from the Session
+ * cache. Should be called after some Hibernate work and before JDBC is used
+ * to check results.
+ *
+ */
+ public void clear() {
+ flush();
+ getTemplate().clear();
+ }
+
+ /**
+ * Evicts the object from the session. This is essential for the
+ * implementation of unit tests where first an object is saved and is
+ * retrieved later. By removing the object from the session, Hibernate must
+ * retrieve the object again from the database.
+ *
+ * @param aObject
+ */
+ protected void evict(Object aObject) {
+ getTemplate().evict(aObject);
+ }
+
+ /**
+ * Gets the connection.
+ *
+ * @return Connection.
+ */
+ public Connection getConnection() {
+ return DataSourceUtils.getConnection(getDataSource());
+ }
+
+ public void cleanDatabase() throws SQLException {
+
+ if (!isDatabaseConfigured()) {
+ return;
+ }
+
+ String[] tables = getTableNames();
+
+ try {
+ IDatabaseConnection connection = new DatabaseConnection(
+ getConnection());
+ ITableFilter filter = new DatabaseSequenceFilter(connection, tables);
+ IDataSet dataset = new FilteredDataSet(filter, connection
+ .createDataSet(tables));
+
+ DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+ } catch (DatabaseUnitException e) {
+ SQLException exc = new SQLException(e.getMessage());
+ exc.initCause(e);
+ throw exc;
+ }
+ }
+
+ /**
+ * @throws SQLException
+ */
+ public String[] getTableNames() throws SQLException {
+
+ List<String> result = new ArrayList<String>();
+ LOG.debug("Getting database table names to clean (schema: '"
+ + SCHEMA_PATTERN + "'");
+
+ ResultSet tables = getConnection().getMetaData().getTables(null,
+ SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+ while (tables.next()) {
+ String table = tables.getString("TABLE_NAME");
+ // Make sure we do not touch hibernate's specific
+ // infrastructure tables.
+ if (!table.toLowerCase().startsWith("hibernate")) {
+ result.add(table);
+ LOG.debug("Adding " + table
+ + " to list of tables to be cleaned.");
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTables(List aTableList) throws SQLException {
+ Iterator liTable = aTableList.iterator();
+ while (liTable.hasNext()) {
+ emptyTable((String) liTable.next());
+ }
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTable(String aTable) throws SQLException {
+ executeSql("delete from " + aTable);
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void dropTable(String aTable) throws SQLException {
+ executeQuery("drop table " + aTable);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction.
+ *
+ * @param aSql
+ * SQL statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql) {
+ return executeSql(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL statement.
+ * @param aArg
+ * Argument of the sql statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql, final Object aArg) {
+ return executeSql(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes an sql statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL query to execute.
+ * @param aArgs
+ * Arguments.
+ * @return Number of rows updated.
+ */
+ public int executeSql(final String aSql, final Object[] aArgs) {
+ Map results = executeTransaction(new TestTransactionCallback() {
+ public Map execute() throws Exception {
+ JdbcTemplate template = new JdbcTemplate(getDataSource());
+ int result = template.update(aSql, aArgs);
+
+ Map<String, Integer> map = new TreeMap<String, Integer>();
+ map.put("result", new Integer(result));
+
+ return map;
+ }
+ });
+
+ return ((Integer) results.get("result")).intValue();
+ }
+
+ /**
+ * Executes a transaction with a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work.
+ * @return Result.
+ */
+ public Object executeTransaction(TransactionCallback aCallback) {
+ TransactionTemplate lTemplate = new TransactionTemplate(
+ getTransactionManager());
+ return lTemplate.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction without a result.
+ *
+ * @param aCallback
+ * Callback to do your transactional work. .
+ */
+ public void executeTransaction(TransactionCallbackWithoutResult aCallback) {
+ TransactionTemplate template = new TransactionTemplate(
+ getTransactionManager());
+ template.execute(aCallback);
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ * @return Result.
+ */
+ public Map executeTransaction(final TestTransactionCallback aCallback) {
+ return (Map) executeTransaction(new TransactionCallback() {
+ public Object doInTransaction(TransactionStatus aArg) {
+ try {
+ return aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes a transaction with a result, causing the testcase to fail if any
+ * type of exception is thrown.
+ *
+ * @param aCallback
+ * Code to be executed within the transaction.
+ */
+ public void executeTransaction(
+ final TestTransactionCallbackWithoutResult aCallback) {
+ executeTransaction(new TransactionCallbackWithoutResult() {
+ public void doInTransactionWithoutResult(TransactionStatus aArg) {
+ try {
+ aCallback.execute();
+ } catch (Exception e) {
+ // test case must fail.
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Executes an SQL query.
+ *
+ * @param aSql
+ * Query to execute.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql) {
+ return executeQuery(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes a query with a single argument. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Query.
+ * @param aArg
+ * Argument.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(String aSql, Object aArg) {
+ return executeQuery(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes a query. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Sql query.
+ * @param aArgs
+ * Arguments to the query.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(final String aSql, final Object[] aArgs) {
+ try {
+ Connection connection = getConnection();
+
+ PreparedStatement statement = connection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ return statement.executeQuery();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the values of a prepared statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aArgs
+ * Arguments to the prepared statement.
+ * @param aStatement
+ * Prepared statement
+ * @throws SQLException
+ */
+ private void setPreparedParams(final Object[] aArgs,
+ PreparedStatement aStatement) throws SQLException {
+ for (int i = 1; i <= aArgs.length; i++) {
+ setPreparedParam(i, aStatement, aArgs[i - 1]);
+ }
+ }
+
+ /**
+ * Sets a prepared statement parameter.
+ *
+ * @param aIndex
+ * Index of the parameter.
+ * @param aStatement
+ * Prepared statement.
+ * @param aObject
+ * Value Must be of type Integer, Long, or String.
+ * @throws SQLException
+ */
+ private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+ Object aObject) throws SQLException {
+ if (aObject instanceof Integer) {
+ aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+ } else if (aObject instanceof Long) {
+ aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+ } else if (aObject instanceof String) {
+ aStatement.setString(aIndex, (String) aObject);
+ } else {
+ TestCase.fail("Unsupported object type for prepared statement: "
+ + aObject.getClass() + " value: " + aObject
+ + " statement: " + aStatement);
+ }
+ }
+
+ private boolean isDatabaseConfigured() {
+ try {
+ getDataSource();
+ } catch (NoSuchBeanDefinitionException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return Returns the dataSource.
+ */
+ public DataSource getDataSource() {
+ return _dataSource;
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public int getTableSize(final String aTable) throws SQLException {
+
+ ResultSet resultSet = executeQuery("select * from " + aTable);
+ int count = 0;
+
+ while (resultSet.next()) {
+ count++;
+ }
+ return count;
+ }
+
+ public int countResultSet(ResultSet aResultSet) throws SQLException {
+ int count = 0;
+
+ while (aResultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
--- /dev/null
+myproperty=Property Value
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="helloService" class="org.wamblee.system.spring.HelloService">
+ <constructor-arg>
+ <value>Hello world!</value>
+ </constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="helloService" class="org.wamblee.system.spring.HelloService">
+ <constructor-arg>
+ <value>${myproperty}</value>
+ </constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="helloService" class="org.wamblee.system.spring.HelloService2">
+ <constructor-arg>
+ <ref bean="properties"/>
+ </constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="blaService" class="org.wamblee.system.spring.BlaService" destroy-method="stop">
+ <constructor-arg>
+ <ref bean="helloService"/>
+ </constructor-arg>
+ </bean>
+
+</beans>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-eclipselink</artifactId>
+ <packaging>jar</packaging>
+ <name>/test/eclipselink</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ <type>test-jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>eclipselink</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>EclipseLink Repo</id>
+ <url>http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo</url>
+ </repository>
+
+ </repositories>
+
+
+</project>
--- /dev/null
+package org.wamblee.support.persistence.eclipselink;
+
+import java.util.Map;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.wamblee.support.persistence.JpaCustomizer;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+
+public class EclipselinkJpaCustomizer implements JpaCustomizer {
+
+ public EclipselinkJpaCustomizer() {
+ // Empty
+ }
+
+ @Override
+ public void customize(PersistenceUnitDescription aPersistenceUnit, Map<String, String> aJpaProperties) {
+ // Hack to make JNDI lookup of the datasource work with toplink
+ aJpaProperties.put("eclipselink.session.customizer", JndiSessionCustomizer.class
+ .getName());
+
+ // DDL generation for toplink
+ aJpaProperties.put("eclipselink.ddl-generation", "create-tables");
+ }
+
+ @Override
+ public ITableFilterSimple getJpaTables() {
+ return new EclipselinkTables();
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence.eclipselink;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * Toplink-specific tables.
+ */
+public class EclipselinkTables implements ITableFilterSimple {
+
+ private static final List<String> TABLES = Arrays.asList(new String[] { "SEQUENCE" } );
+
+ public boolean accept(String aTableName) throws DataSetException {
+ return TABLES.contains(aTableName);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence.eclipselink;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import org.eclipse.persistence.config.SessionCustomizer;
+import org.eclipse.persistence.sessions.DatabaseLogin;
+import org.eclipse.persistence.sessions.JNDIConnector;
+import org.eclipse.persistence.sessions.Session;
+import org.eclipse.persistence.sessions.server.ServerSession;
+
+/**
+ * See http://wiki.eclipse.org/Customizing_the_EclipseLink_Application_(ELUG) Use for clients that would like to use a
+ * JTA SE pu instead of a RESOURCE_LOCAL SE pu.
+ *
+ * This utility also makes sure that using a persistence.xml with a JTA datasource works in a standalone Java SE
+ * environment together with our JNDI stub.
+ */
+public class JndiSessionCustomizer
+ implements SessionCustomizer {
+
+ public JndiSessionCustomizer() {
+ // Empty.
+ }
+
+ /**
+ * Get a dataSource connection and set it on the session with lookupType=STRING_LOOKUP
+ */
+ public void customize(Session session) throws Exception {
+ JNDIConnector connector = null;
+ Context context = null;
+ try {
+ context = new InitialContext();
+ if(null != context) {
+ connector = (JNDIConnector)session.getLogin().getConnector(); // possible CCE
+ // Change from COMPOSITE_NAME_LOOKUP to STRING_LOOKUP
+ // Note: if both jta and non-jta elements exist this will only change the first one - and may still result in
+ // the COMPOSITE_NAME_LOOKUP being set
+ // Make sure only jta-data-source is in persistence.xml with no non-jta-data-source property set
+ connector.setLookupType(JNDIConnector.STRING_LOOKUP);
+
+ // Or, if you are specifying both JTA and non-JTA in your persistence.xml then set both connectors to be safe
+ JNDIConnector writeConnector = (JNDIConnector)session.getLogin().getConnector();
+ writeConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
+ JNDIConnector readConnector =
+ (JNDIConnector)((DatabaseLogin)((ServerSession)session).getReadConnectionPool().getLogin()).getConnector();
+ readConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
+
+ System.out.println("JndiSessionCustomizer: configured " + connector.getName());
+ }
+ else {
+ throw new Exception("JndiSessionCustomizer: Context is null");
+ }
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+org.wamblee.support.persistence.eclipselink.EclipselinkJpaCustomizer
--- /dev/null
+package org.wamblee.support.persistence.eclipselink;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.DatabaseUtilsTestBase;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+public class DatabaseUtilsTest extends DatabaseUtilsTestBase {
+ // Empty, all tests inherited
+}
--- /dev/null
+package org.wamblee.support.persistence.eclipselink;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseTestCase;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.JpaTester;
+import org.wamblee.support.persistence.MyEntityExampleTestBase;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+import static junit.framework.Assert.*;
+
+
+/**
+ * This class shows an example of how to test an entity using jpa.
+ */
+public class MyEntityExampleTest extends MyEntityExampleTestBase {
+ // Empty, all tests are inherited
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <packaging>jar</packaging>
+ <name>/test/enterprise</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ <version>0.2</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>transaction-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbyclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derbynet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-dbcp</groupId>
+ <artifactId>commons-dbcp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+package org.wamblee.support.jndi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.NamingException;
+
+public class StubInitialContext extends InitialContext {
+ private Map<String, Object> bindings = new HashMap<String, Object>();
+
+ public StubInitialContext() throws NamingException {
+ super(true);
+ }
+
+ @Override
+ public void bind(String name, Object obj) throws NamingException {
+ bindings.put(name, obj);
+ }
+
+ @Override
+ public Object lookup(String name) throws NamingException {
+ return bindings.get(name);
+ }
+
+ @Override
+ public Object lookup(Name name) throws NamingException {
+ return super.lookup(name.toString());
+ }
+}
--- /dev/null
+package org.wamblee.support.jndi;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+
+/**
+ * Test initial context factory used for testing software in a Java SE
+ * environnment that uses JNDI to retrieve objects.
+ *
+ * See {@link #bind(String, Object)} to resp. register the initial context.
+ *
+ * To bind objects in the JNDI tree simply use the standard JNDI api: <code>
+ * InitialContext context = new InitialContext();
+ * MyClass myObj = ...;
+ * context.bind("a/b", myObj);
+ * </code>
+ */
+public class StubInitialContextFactory implements InitialContextFactory {
+
+ private static Context context;
+
+ private static void initialize() {
+ try {
+ context = new StubInitialContext();
+ } catch (NamingException e) { // can't happen.
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * This method must be called to register this initial context factory as
+ * the default implementation for JNDI.
+ *
+ * @throws Exception
+ */
+ public static void register() {
+ // sets up the InitialContextFactoryForTest as default factory.
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
+ StubInitialContextFactory.class.getName());
+ if (context == null) {
+ initialize();
+ }
+ }
+
+ /**
+ * Unregisters the initial context factory
+ */
+ public static void unregister() {
+ System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "");
+ context = null;
+ }
+
+ public Context getInitialContext(Hashtable<?, ?> environment)
+ throws NamingException {
+ return context;
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+public abstract class AbstractDatabase implements Database {
+ private static final int CONNECTION_POOL_SIZE = 16;
+
+ private DataSource itsDataSource;
+
+ private boolean started;
+
+ protected AbstractDatabase() {
+ started = false;
+ }
+
+ protected abstract void doStart();
+
+ protected abstract void doStop();
+
+ /**
+ * This method must be called from the start method.
+ */
+ protected final void createDataSource() {
+ GenericObjectPool connectionPool = new GenericObjectPool(null);
+ connectionPool.setMaxActive(CONNECTION_POOL_SIZE);
+ ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(getJdbcUrl(), getUsername(), getPassword());
+ // The following line must be kept in although it does not appear to be used, the constructor regsiters the
+ // constructed object at the connection pool.
+ PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,connectionPool,null,null,false,true);
+ itsDataSource = new PoolingDataSource(connectionPool);
+ }
+
+
+ /// BELOW THIS LINE IS NOT OF INTEREST TO SUBCLASSES.
+
+ public final DataSource start() {
+ if ( started ) {
+ throw new RuntimeException("Database already started");
+ }
+ started = true;
+ doStart();
+ return getDatasource();
+ }
+
+ public final void stop() {
+ if ( ! started ) {
+ return; // nothing to do.
+ }
+ started = false;
+ doStop();
+ }
+
+ private final DataSource getDatasource() {
+ if ( !started) {
+ throw new RuntimeException("Database is not started!");
+ }
+ return itsDataSource;
+ }
+
+ protected String getProperty(String aName) {
+ String value = System.getProperty(aName);
+ if ( value != null ) {
+ return value;
+ }
+ value = System.getenv(aName);
+ if ( value != null ) {
+ return value;
+ }
+ throw new RuntimeException("This class expects the '" + aName + "' property to be set");
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.List;
+
+public abstract class AbstractDatabaseProvider implements DatabaseProvider {
+
+ protected abstract List<String> getCapabilities();
+
+ public final boolean supportsCapabilities(String[] aCapabilities) {
+ for (String capability: aCapabilities) {
+ if ( !getCapabilities().contains(capability)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.List;
+import java.util.Map;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+public class CompositeJpaCustomizer implements JpaCustomizer {
+
+ private List<JpaCustomizer> _customizers;
+ private CompositeJpaTables _tables;
+
+ public CompositeJpaCustomizer(List<JpaCustomizer> aCustomizers) {
+ _customizers = aCustomizers;
+ _tables = new CompositeJpaTables();
+ for (JpaCustomizer customizer: _customizers) {
+ _tables.add(customizer.getJpaTables());
+ }
+ }
+
+ @Override
+ public void customize(PersistenceUnitDescription aPersistenceUnit, Map<String, String> aJpaProperties) {
+ for (JpaCustomizer customizer: _customizers) {
+ customizer.customize(aPersistenceUnit, aJpaProperties);
+ }
+ }
+
+ @Override
+ public ITableFilterSimple getJpaTables() {
+ return _tables;
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+public class CompositeJpaTables implements ITableFilterSimple {
+
+ private List<ITableFilterSimple> _tables;
+
+ public CompositeJpaTables() {
+ _tables = new ArrayList<ITableFilterSimple>();
+ }
+
+ public void add(ITableFilterSimple aFilter) {
+ _tables.add(aFilter);
+ }
+
+ @Override
+ public boolean accept(String aTableName) throws DataSetException {
+ for (ITableFilterSimple filter: _tables) {
+ if (filter.accept(aTableName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.support.persistence;
+
+import javax.sql.DataSource;
+
+/**
+ * Represents a database.
+ *
+ * @author Erik Brakkee
+ */
+public interface Database {
+
+ /**
+ * Starts a database. This call should not block and return as soon as the
+ * database has been started.
+ */
+ DataSource start();
+
+ /**
+ * Gets the Jdbc Url to connect to this database.
+ *
+ * @return Jdbc Url.
+ */
+ String getJdbcUrl();
+
+ /**
+ * Gets the external Jdbc URL to connect to this database from other JVMs.
+ */
+ String getExternalJdbcUrl();
+
+ /**
+ * Gets the username to connect to the database.
+ *
+ * @return username.
+ */
+ String getUsername();
+
+ /**
+ * Gets the password to connect to the database.
+ *
+ * @return password.
+ */
+ String getPassword();
+
+ /**
+ * Stops a database.
+ */
+ void stop();
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.logging.Logger;
+
+/**
+ * DatabaseBuilder is used from unit test to obtain a reference to a database
+ * from unit test. This database is either an inmemory database or represents an
+ * external database. Purpose of this utility is to make test code independent of the
+ * particular database used and specifically to be able to run database tests without
+ * any configuration at all (using an inmemory database).
+ *
+ * The type of database to use can be overridden by specifying either a system
+ * property or an environment variable ({@link #DB_CAPABILITIES_PROP}) that
+ * contains the comma-separated capabilities of the database. Each database type
+ * provides its own capabilities (see {@link DatabaseProvider} implementations}.
+ *
+ *
+ * There are currently two database types available:
+ * <ul>
+ * <li> Derby: AN inmemory derby. Provided by {@link DerbyDatabaseProvider}. This is the default.
+ * </li>
+ * <li> External: An arbitrary external database configured using system properties or environment variables
+ * in the usual way using a JDBC URL, username, and password.
+ * </li>
+ * </ul>
+ *
+ * The <code>DatabaseBuilder</code> uses the {@link ServiceLoader} mechanism to find implementations
+ * of {@link DatabaseProvider} on the classpath. In the {@link #getDatabase(String...)} method a number of
+ * capabilities are passed. The database providers are then searched in (arbitrary) order and the first one that
+ * has all required capabilities is returned.
+ *
+ * {@link #getSupportedDatabases()} gives a list of all available databases.
+ */
+public class DatabaseBuilder {
+
+ private static final Logger LOGGER = Logger.getLogger(DatabaseBuilder.class
+ .getName());
+
+ /**
+ * Environmment variable by which capabilities of the requested database can
+ * be defined
+ */
+ private static final String DB_CAPABILITIES_PROP = "TEST_DB_CAPABILITIES";
+
+ private static ServiceLoader<DatabaseProvider> LOADER = null;
+
+ private DatabaseBuilder() {
+ // Empty.
+ }
+
+ private static String[] parseCapabilities(String aValue) {
+ return aValue.split(",");
+ }
+
+ /**
+ * Gets the first database that has all required capabilities.
+ * @param aCapabilities Capabilities.
+ * @return Database to use.
+ */
+ public static Database getDatabase(String... aCapabilities) {
+ if (aCapabilities.length == 0) {
+ LOGGER.info("Examining database capabilities");
+ LOGGER.info(" Checking system property " + DB_CAPABILITIES_PROP);
+ String capabilities = System.getProperty(DB_CAPABILITIES_PROP);
+ if (capabilities != null) {
+ aCapabilities = parseCapabilities(capabilities);
+ } else {
+ LOGGER.info(" Checking environment variable "
+ + DB_CAPABILITIES_PROP);
+ capabilities = System.getenv(DB_CAPABILITIES_PROP);
+ if (capabilities != null) {
+ aCapabilities = parseCapabilities(capabilities);
+ } else {
+ LOGGER.info(" Using default capabilities");
+ aCapabilities = new String[] { DatabaseProvider.CAPABILITY_IN_MEMORY };
+ }
+ }
+ LOGGER.info("Using capabilities: " + aCapabilities);
+ }
+ synchronized (DatabaseBuilder.class) {
+ initLoader();
+ for (DatabaseProvider db : LOADER) {
+ if (db.supportsCapabilities(aCapabilities)) {
+ return db.create();
+ }
+ }
+ }
+ throw new RuntimeException(
+ "No database found that satisfies capabilities: "
+ + Arrays.asList(aCapabilities));
+ }
+
+ /**
+ * Gets a list of available databases.
+ * @return List of databases.
+ */
+ public static List<DatabaseDescription> getSupportedDatabases() {
+ initLoader();
+ List<DatabaseDescription> descriptions = new ArrayList<DatabaseDescription>();
+ for (DatabaseProvider db : LOADER) {
+ descriptions.add(db.getDescription());
+ }
+ return descriptions;
+ }
+
+ private static void initLoader() {
+ if (LOADER == null) {
+ LOADER = ServiceLoader.load(DatabaseProvider.class);
+ }
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+/**
+ * Description of a specific database.
+ */
+public class DatabaseDescription {
+
+ private String[] itsCapabilities;
+ private String itsDatabase;
+ private String itsOther;
+
+ /**
+ * Constructs the description.
+ * @param aCapabilities List of all capabilities.
+ * @param aDatabase Database.
+ * @param aOther Other information.
+ */
+ public DatabaseDescription(String[] aCapabilities, String aDatabase, String aOther) {
+ itsCapabilities = aCapabilities;
+ itsDatabase = aDatabase;
+ itsOther = aOther;
+ }
+
+ @Override
+ public String toString() {
+ return "\n Database " + itsDatabase +
+ "\n Capabilities: " + printCapabilities() +
+ "\n Other info: " + itsOther;
+ }
+
+ private String printCapabilities() {
+ String res = "";
+ for (int i = 0; i < itsCapabilities.length; i++) {
+ res += "" + itsCapabilities[i];
+ if ( i < itsCapabilities.length -1 ) {
+ res += ", ";
+ }
+ }
+ return res;
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+/**
+ * Database provider. This database provider represents a particular type of
+ * database such as its capabilities and the ability to create an instance
+ * representing a running database.
+ *
+ * Since the {@link DatabaseBuilder} uses a first match algorithm and the order
+ * of databaseproviders is not guaranteed, it is recommended for each database
+ * provider to also provide a unique capability that no other database has.
+ */
+public interface DatabaseProvider {
+
+ /**
+ * Capability that all databases that run inmemory have.
+ */
+ String CAPABILITY_IN_MEMORY = "INMEMORY";
+
+ /**
+ * Capability that all databases that are external have.
+ */
+ String CAPABILITY_EXTERNAL = "EXTERNAL";
+
+ /**
+ * Determines if the database has all capabilities that are requested.
+ * @param aCapabilities Capabilities it must ahve
+ * @return True if it has all capabilities.
+ */
+ boolean supportsCapabilities(String[] aCapabilities);
+
+ /**
+ * Gets the description for the database.
+ * @return Description.
+ */
+ DatabaseDescription getDescription();
+
+ /**
+ * Creates a database instance that represents a running instance of that database.
+ * @return Database.
+ */
+ Database create();
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.support.persistence;
+
+
+/**
+ * This class is used for starting the database from ant.
+ */
+public class DatabaseStarter {
+
+ /**
+ * Database class which encapsulates management of the database.
+ */
+ private Class _databaseClass;
+
+ /**
+ * Execution as a main program. Commandline
+ *
+ * <pre>
+ *
+ * DatabaseStarter <databaseClassName>
+ *
+ * </pre>
+ *
+ * where the database class name must be the name of a concrete subclass of
+ * {@link Database}.
+ *
+ * @param args
+ */
+ public static void main( String[] args ) throws Exception {
+ String clazz = args[0];
+ try {
+ new DatabaseStarter( Class.forName( clazz ) ).start( );
+ } catch ( Exception e ) {
+ e.printStackTrace( );
+ System.out
+ .println( "\nUsage: ant dbClass ");
+ }
+ }
+
+ /**
+ * Constructs the database starter.
+ *
+ * @param aClassName
+ * Classname of the database class to use.
+ * @throws Exception
+ */
+ public DatabaseStarter( Class aClass ) throws Exception {
+ if ( !Database.class.isAssignableFrom( aClass ) ) {
+ throw new IllegalArgumentException( "Class '"
+ + aClass.getName( )
+ + "' is not a subclass of Database" );
+ }
+ _databaseClass = aClass;
+ }
+
+ /**
+ * Constructs a database starter with the derby database.
+ *
+ * @throws Exception
+ */
+ public DatabaseStarter( ) throws Exception {
+ this( DerbyDatabase.class );
+ }
+
+ /**
+ * Starts the database.
+ *
+ * @throws Exception
+ */
+ public void start( ) throws Exception {
+ Database lDatabase = (Database) _databaseClass.newInstance( );
+ lDatabase.start( );
+ System.out.println( "Database has been started. " );
+ System.out.println( );
+ System.out.println("=======================================================");
+ System.out.println( "Connection details:" );
+ // System.out.println( " Driver class: "
+ // + lDatabase.getDriverClassName( ) );
+ System.out.println( " JDBC URL: "
+ + lDatabase.getExternalJdbcUrl( ) );
+ System.out.println( " username: '" + lDatabase.getUsername( )
+ + "'" );
+ System.out.println( " password: '" + lDatabase.getPassword( )
+ + "'" );
+ System.out.println( "Interrupt the program to stop the database." );
+ System.out.println("=======================================================");
+ System.out.println("You must now populate the database with a schema. Use 'ant help' for information.");
+ for ( ;; ) {
+ Thread.sleep( 1000 );
+ }
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import junit.framework.TestCase;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.DatabaseSequenceFilter;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.FilteredDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.filter.ITableFilter;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.dbunit.operation.DatabaseOperation;
+
+/**
+ * Database utilities is a simple support class for common tasks in working with
+ * databases.
+ */
+public class DatabaseUtils {
+
+ public static interface TableSet {
+ boolean contains(String aTableName);
+ }
+
+ public static interface JdbcUnitOfWork<T> {
+ T execute(Connection aConnection) throws Exception;
+ }
+
+ public static interface TableSetOperation {
+ void execute(String aTable) throws Exception;
+ }
+
+ private static final Logger LOG = Logger.getLogger(DatabaseUtils.class
+ .getName());
+
+ /**
+ * Schema pattern.
+ */
+ private static final String SCHEMA_PATTERN = "%";
+ private DataSource dataSource;
+ private ITableFilterSimple tables;
+
+
+ public DatabaseUtils(DataSource aDataSource, ITableFilterSimple aTables) {
+ dataSource = aDataSource;
+ tables = aTables;
+ }
+
+ public IDatabaseTester createDbTester() throws Exception {
+ return createDbTester(getTableNames(tables));
+ }
+
+ public IDatabaseTester createDbTester(String[] aTables) throws Exception {
+ IDatabaseTester dbtester = new DataSourceDatabaseTester(dataSource);
+ dbtester.setDataSet(dbtester.getConnection().createDataSet(aTables));
+ return dbtester;
+ }
+
+ public void cleanDatabase() throws Exception {
+ cleanDatabase(tables);
+ }
+
+ public void executeOnTables(ITableFilterSimple aTables,
+ final TableSetOperation aOperation) throws Exception {
+ final String[] tables = getTableNames(aTables);
+ executeInTransaction(new JdbcUnitOfWork<Void>() {
+ public Void execute(Connection aConnection) throws Exception {
+ for (int i = tables.length - 1; i >= 0; i--) {
+ aOperation.execute(tables[i]);
+ }
+ return null;
+ }
+ });
+ for (String table : tables) {
+
+ }
+ }
+
+ public void cleanDatabase(ITableFilterSimple aSelection) throws Exception {
+
+ final String[] tables = getTableNames(aSelection);
+ executeInTransaction(new JdbcUnitOfWork<Void>() {
+
+ public Void execute(Connection aConnection) throws Exception {
+ IDatabaseConnection connection = new DatabaseConnection(
+ aConnection);
+ ITableFilter filter = new DatabaseSequenceFilter(connection,
+ tables);
+ IDataSet dataset = new FilteredDataSet(filter, connection
+ .createDataSet(tables));
+ DatabaseOperation.DELETE_ALL.execute(connection, dataset);
+ return null;
+ }
+ });
+
+ }
+
+ public <T> T executeInTransaction(JdbcUnitOfWork<T> aCallback)
+ throws Exception {
+ Connection connection = dataSource.getConnection();
+ try {
+ T value = aCallback.execute(connection);
+ connection.commit();
+ return value;
+ } finally {
+ connection.close();
+ }
+ }
+
+ public String[] getTableNames() throws Exception {
+ return getTableNames(tables);
+ }
+
+ /**
+ * @throws SQLException
+ */
+ public String[] getTableNames(ITableFilterSimple aSelection)
+ throws Exception {
+
+ List<String> result = new ArrayList<String>();
+ LOG.fine("Getting database table names to clean (schema: '"
+ + SCHEMA_PATTERN + "'");
+
+ ResultSet tables = dataSource.getConnection().getMetaData().getTables(
+ null, SCHEMA_PATTERN, "%", new String[] { "TABLE" });
+ while (tables.next()) {
+ String table = tables.getString("TABLE_NAME");
+ if (aSelection.accept(table)) {
+ result.add(table);
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ public void emptyTables() throws Exception {
+ executeOnTables(tables, new TableSetOperation() {
+ public void execute(String aTable) throws Exception {
+ emptyTable(aTable);
+ }
+ });
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTables(final ITableFilterSimple aSelection)
+ throws Exception {
+ executeOnTables(aSelection, new TableSetOperation() {
+ public void execute(String aTable) throws Exception {
+ emptyTable(aTable);
+ }
+ });
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void emptyTable(String aTable) throws Exception {
+ executeSql("delete from " + aTable);
+ }
+
+ public void dropTables() throws Exception {
+ executeOnTables(tables, new TableSetOperation() {
+
+ public void execute(String aTable) throws Exception {
+ dropTable(aTable);
+ }
+ });
+ }
+
+
+ public void dropTables(ITableFilterSimple aTables) throws Exception {
+ executeOnTables(aTables, new TableSetOperation() {
+
+ public void execute(String aTable) throws Exception {
+ dropTable(aTable);
+ }
+ });
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public void dropTable(final String aTable) throws Exception {
+ executeInTransaction(new JdbcUnitOfWork<Void>() {
+ public Void execute(Connection aConnection) throws Exception {
+ executeUpdate(aConnection, "drop table " + aTable);
+ return null;
+ }
+ });
+
+ }
+
+ /**
+ * Executes an SQL statement within a transaction.
+ *
+ * @param aSql
+ * SQL statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql) throws Exception {
+ return executeSql(aSql, new Object[0]);
+ }
+
+ /**
+ * Executes an SQL statement within a transaction. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL statement.
+ * @param aArg
+ * Argument of the sql statement.
+ * @return Return code of the corresponding JDBC call.
+ */
+ public int executeSql(final String aSql, final Object aArg)
+ throws Exception {
+ return executeSql(aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes an sql statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * SQL query to execute.
+ * @param aArgs
+ * Arguments.
+ * @return Number of rows updated.
+ */
+ public int executeSql(final String aSql, final Object[] aArgs)
+ throws Exception {
+ return executeInTransaction(new JdbcUnitOfWork<Integer>() {
+ public Integer execute(Connection aConnection) throws Exception {
+ PreparedStatement stmt = aConnection.prepareStatement(aSql);
+ setPreparedParams(aArgs, stmt);
+ return stmt.executeUpdate();
+ }
+ });
+ }
+
+ /**
+ * Executes an SQL query.
+ *
+ * @param aSql
+ * Query to execute.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(Connection aConnection, String aSql) {
+ return executeQuery(aConnection, aSql, new Object[0]);
+ }
+
+ /**
+ * Executes a query with a single argument. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Query.
+ * @param aArg
+ * Argument.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(Connection aConnection, String aSql,
+ Object aArg) {
+ return executeQuery(aConnection, aSql, new Object[] { aArg });
+ }
+
+ /**
+ * Executes a query. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aSql
+ * Sql query.
+ * @param aArgs
+ * Arguments to the query.
+ * @return Result set.
+ */
+ public ResultSet executeQuery(Connection aConnection, final String aSql,
+ final Object[] aArgs) {
+ try {
+ PreparedStatement statement = aConnection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ return statement.executeQuery();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int executeUpdate(Connection aConnection, final String aSql,
+ final Object... aArgs) {
+ try {
+ PreparedStatement statement = aConnection.prepareStatement(aSql);
+ setPreparedParams(aArgs, statement);
+
+ return statement.executeUpdate();
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the values of a prepared statement. See
+ * {@link #setPreparedParam(int, PreparedStatement, Object)}for details on
+ * supported argument types.
+ *
+ * @param aArgs
+ * Arguments to the prepared statement.
+ * @param aStatement
+ * Prepared statement
+ * @throws SQLException
+ */
+ private void setPreparedParams(final Object[] aArgs,
+ PreparedStatement aStatement) throws SQLException {
+ for (int i = 1; i <= aArgs.length; i++) {
+ setPreparedParam(i, aStatement, aArgs[i - 1]);
+ }
+ }
+
+ /**
+ * Sets a prepared statement parameter.
+ *
+ * @param aIndex
+ * Index of the parameter.
+ * @param aStatement
+ * Prepared statement.
+ * @param aObject
+ * Value Must be of type Integer, Long, or String.
+ * @throws SQLException
+ */
+ private void setPreparedParam(int aIndex, PreparedStatement aStatement,
+ Object aObject) throws SQLException {
+ if (aObject instanceof Integer) {
+ aStatement.setInt(aIndex, ((Integer) aObject).intValue());
+ } else if (aObject instanceof Long) {
+ aStatement.setLong(aIndex, ((Integer) aObject).longValue());
+ } else if (aObject instanceof String) {
+ aStatement.setString(aIndex, (String) aObject);
+ } else {
+ TestCase.fail("Unsupported object type for prepared statement: "
+ + aObject.getClass() + " value: " + aObject
+ + " statement: " + aStatement);
+ }
+ }
+
+ /**
+ * @return
+ * @throws SQLException
+ */
+ public int getTableSize(final String aTable) throws Exception {
+ return executeInTransaction(new JdbcUnitOfWork<Integer>() {
+ public Integer execute(Connection aConnection) throws Exception {
+ ResultSet resultSet = executeQuery(aConnection,
+ "select count(*) from " + aTable);
+ resultSet.next();
+ return resultSet.getInt(1);
+ }
+ });
+
+ }
+
+ public int countResultSet(ResultSet aResultSet) throws SQLException {
+ int count = 0;
+
+ while (aResultSet.next()) {
+ count++;
+ }
+
+ return count;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2005 the original author or authors.
+ *
+ * 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.wamblee.support.persistence;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import junit.framework.TestCase;
+
+import org.apache.derby.drda.NetworkServerControl;
+import org.wamblee.io.FileSystemUtils;
+
+/**
+ * Derby database setup. The external JDBC url used to connect to a running
+ * instance is
+ *
+ * <pre>
+ * jdbc:derby:net://localhost:1527/testdb
+ * </pre>
+ *
+ * and the driver class is
+ *
+ * <pre>
+ * com.ibm.db2.jcc.DB2Driver
+ * </pre>
+ *
+ * The following jars will have to be used <code>db2jcc.jar</code> and
+ * <code>db2jcc_license_c.jar</code>.
+ */
+public class DerbyDatabase extends AbstractDatabase {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOGGER = Logger.getLogger(DerbyDatabase.class
+ .getName());
+
+ /**
+ * Database user name.
+ */
+ private static final String USERNAME = "sa";
+
+ /**
+ * Database password.
+ */
+ private static final String PASSWORD = "123";
+ /**
+ * Poll interval for the checking the server status.
+ */
+ private static final int POLL_INTERVAL = 100;
+
+ /**
+ * Maximum time to wait until the server has started or stopped.
+ */
+ private static final int MAX_WAIT_TIME = 10000;
+
+ /**
+ * Database name to use.
+ */
+ private static final String DATABASE_NAME = "testdb";
+
+ /**
+ * Path on the file system where derby files are stored.
+ */
+ private static final String DATABASE_PATH = "target/db/persistence/derby";
+
+ /**
+ * Derby property required to set the file system path
+ * {@link #DATABASE_PATH}.
+ */
+ private static final String SYSTEM_PATH_PROPERTY = "derby.system.home";
+
+
+ private boolean inmemory;
+
+
+ /**
+ * Constructs derby database class to allow creation of derby database
+ * instances.
+ */
+ public DerbyDatabase() {
+ inmemory = true;
+ }
+
+ public DerbyDatabase(boolean aInMemoryFlag) {
+ inmemory = aInMemoryFlag;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#start()
+ */
+ public void doStart() {
+ try {
+ // just in case a previous run was killed without the
+ // cleanup
+ cleanPersistentStorage();
+
+ if (!inmemory) {
+ // set database path.
+ Properties lProperties = System.getProperties();
+ lProperties.put(SYSTEM_PATH_PROPERTY, DATABASE_PATH);
+ }
+
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+
+ runDatabase();
+
+ waitUntilStartedOrStopped(true);
+
+ // Force creation of the database.
+ Connection lConnection = createConnection();
+ lConnection.close();
+
+ LOGGER.info("Database started: \n URL = " + getExternalJdbcUrl() + "\n user = " + getUsername() + "\n password = "
+ + getPassword());
+
+ createDataSource();
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ if (isStarted()) {
+ LOGGER.warning("Shutting down db");
+ DerbyDatabase.this.stop();
+ }
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException("Problem starting database", e);
+ }
+ }
+
+ /**
+ * Waits until the database server has started or stopped.
+ *
+ * @param aStarted
+ * If true, waits until the server is up, if false, waits until
+ * the server is down.
+ * @throws InterruptedException
+ */
+ private void waitUntilStartedOrStopped(boolean aStarted)
+ throws InterruptedException {
+ long lWaited = 0;
+
+ while (aStarted != isStarted()) {
+ Thread.sleep(POLL_INTERVAL);
+ lWaited += POLL_INTERVAL;
+
+ if (lWaited > MAX_WAIT_TIME) {
+ throw new RuntimeException(
+ "Derby database did not start within " + MAX_WAIT_TIME
+ + "ms");
+ }
+ }
+ }
+
+ /**
+ * Checks if the database server has started or not.
+ *
+ * @return True if started, false otherwise.
+ */
+ private boolean isStarted() {
+ try {
+ getControl().ping();
+
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the controller for the database server.
+ *
+ * @return Controller.
+ * @throws Exception
+ */
+ private NetworkServerControl getControl() throws Exception {
+ return new NetworkServerControl();
+ }
+
+ /**
+ * Runs the database.
+ *
+ */
+ private void runDatabase() {
+ try {
+ getControl().start(new PrintWriter(System.out));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#getJdbcUrl()
+ */
+ public String getJdbcUrl() {
+ return getBaseJdbcUrl()
+ + ";create=true;retrieveMessagesFromServerOnGetMessage=true;";
+ }
+
+ private String getBaseJdbcUrl() {
+ return (inmemory ? "jdbc:derby:memory:": "jdbc:derby:") + DATABASE_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#getExternalJdbcUrl()
+ */
+ public String getExternalJdbcUrl() {
+ return "jdbc:derby://localhost:1527/" + (inmemory ? "memory:" : "") + DATABASE_NAME;
+ }
+
+ /**
+ * Shuts down the derby database and cleans up all created files.
+ *
+ */
+ private void shutdownDerby() {
+ try {
+ DriverManager.getConnection("jdbc:derby:;shutdown=true");
+ throw new RuntimeException("Derby did not shutdown, "
+ + " should always throw exception at shutdown");
+ } catch (Exception e) {
+ LOGGER.info("Derby has been shut down.");
+ }
+ }
+
+ /**
+ * Gets the user name.
+ */
+ public String getUsername() {
+ return USERNAME;
+ }
+
+ /**
+ * Gets the password.
+ *
+ * @return
+ */
+ public String getPassword() {
+ return PASSWORD;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.persistence.Database#createConnection()
+ */
+ public Connection createConnection() throws SQLException {
+ Connection c = DriverManager.getConnection(getJdbcUrl(), getUsername(),
+ getPassword());
+
+ return c;
+ }
+
+
+ /**
+ * Stops the derby database and cleans up all derby files.
+ */
+ public void doStop() {
+ try {
+ // shutdown network server.
+ getControl().shutdown();
+ waitUntilStartedOrStopped(false);
+
+ // shutdown inmemory access.
+ shutdownDerby();
+ cleanPersistentStorage();
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Problem stopping database", e);
+ }
+ }
+
+ /**
+ * Cleans up persistent storage of Derby.
+ */
+ private void cleanPersistentStorage() {
+ File lFile = new File(DATABASE_PATH);
+
+ if (lFile.isFile()) {
+ TestCase.fail("A regular file by the name " + DATABASE_PATH
+ + " exists, clean this up first");
+ }
+
+ if (!lFile.isDirectory()) {
+ return; // no-op already cleanup up.
+ }
+ FileSystemUtils.deleteDirRecursively(DATABASE_PATH);
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DerbyDatabaseProvider extends AbstractDatabaseProvider {
+
+ /**
+ * Capabilities of this type of database.
+ */
+ public static final List<String> CAPABILIITIES =
+ Arrays.asList(DatabaseProvider.CAPABILITY_IN_MEMORY, "DERBY");
+
+
+ public DerbyDatabaseProvider() {
+ // Empty
+ }
+
+ public Database create() {
+ return new DerbyDatabase();
+ }
+
+ public DatabaseDescription getDescription() {
+ return new DatabaseDescription(CAPABILIITIES.toArray(new String[0]),
+ "Derby", "In-memory, volatile, set breakpoint to debug");
+ }
+
+ @Override
+ protected List<String> getCapabilities() {
+ return CAPABILIITIES;
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+/**
+ * Database that encapsulates connection to an external database.
+ * Database connection details can be configured through system properties
+ * and environment variables, see {@link #DB_URL_PROP}, {@link #DB_USER_PROP}, and
+ * {@link #DB_PASSWORD_PROP|.
+ *
+ * This class assumes modern database drivers that work together with java.util.ServiceLoader
+ * so that explicitly doing a Class.forName() is not necessary to load the database driver.
+ */
+public class ExternalDatabase extends AbstractDatabase {
+
+ private static final Logger LOGGER = Logger.getLogger(ExternalDatabase.class.getName());
+
+ /**
+ * System property/environment variable that defines the database URL.
+ */
+ public static final String DB_URL_PROP = "TEST_DB_URL";
+
+ /**
+ * System property/environment variable that defines the database user.
+ */
+ public static final String DB_USER_PROP = "TEST_DB_USER";
+
+ /**
+ * System property/environment variable that defines the database password.
+ */
+ public static final String DB_PASSWORD_PROP = "TEST_DB_PASSWORD";
+
+
+ private String itsUrl;
+ private String itsUser;
+ private String itsPassword;
+
+ private DataSource itsDataSource;
+
+ public ExternalDatabase() {
+ // Empty
+ }
+
+ public String getExternalJdbcUrl() {
+ return itsUrl;
+ }
+
+ public String getJdbcUrl() {
+ return itsUrl;
+ }
+
+ public String getPassword() {
+ return itsPassword;
+ }
+
+ public String getUsername() {
+ return itsUser;
+ }
+
+ public void doStart() {
+ itsUrl = getProperty(DB_URL_PROP);
+ itsUser = getProperty(DB_USER_PROP);
+ itsPassword = getProperty(DB_PASSWORD_PROP);
+
+ createDataSource();
+ }
+
+ public void doStop() {
+ // Empty.
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ExternalDatabaseProvider extends AbstractDatabaseProvider {
+
+ /**
+ * Capabilities of this type of database.
+ */
+ public static final List<String> CAPABILIITIES = Arrays
+ .asList(CAPABILITY_EXTERNAL);
+
+ @Override
+ protected List<String> getCapabilities() {
+ return CAPABILIITIES;
+ }
+
+ public Database create() {
+ return new ExternalDatabase();
+ }
+
+ public DatabaseDescription getDescription() {
+ return new DatabaseDescription(
+ CAPABILIITIES.toArray(new String[0]),
+ "External Database",
+ "Any database as described by the JDBC URL: requires system properties or environment variables: "
+ + ExternalDatabase.DB_URL_PROP
+ + ", "
+ + ExternalDatabase.DB_USER_PROP
+ + ", and "
+ + ExternalDatabase.DB_PASSWORD_PROP);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.wamblee.support.jndi.StubInitialContextFactory;
+
+
+/**
+ * Utility for building an appropriately configured EntityManagerFactory. The
+ * idea is that a persistence.xml is used unchanged from the production version.
+ * This utility will then add the additional properties required for execution
+ * in a standalone environment.
+ *
+ * The other purpose is to to shield dependencies of the test code on a
+ * particular JPA provider.
+ */
+public class JpaBuilder {
+
+ private static final Logger LOGGER = Logger.getLogger(JpaBuilder.class
+ .getName());
+
+ /**
+ * Callback interface to execute some JPA code within a transaction with the
+ * entitymanager to use provided as input.
+ */
+ public static interface JpaUnitOfWork<T> {
+ /**
+ * Executes the unit of work. A transaction has been started.
+ * @param em Entity manager.
+ * @return Result of the execute method. If you don't want to return anything use
+ * <code>Void</code> for the return type and return null from the implementation.
+ */
+ T execute(EntityManager em);
+ }
+
+ private PersistenceUnitDescription persistenceUnit;
+ private DataSource dataSource;
+ private EntityManagerFactory factory;
+
+ /**
+ * Constructs the builder.
+ *
+ * @param aDataSource
+ * Datasource of database.
+ * @param aPersistenceUnit
+ * Persistence unit.
+ */
+ public JpaBuilder(DataSource aDataSource,
+ PersistenceUnitDescription aPersistenceUnit) {
+ persistenceUnit = aPersistenceUnit;
+ dataSource = aDataSource;
+ StubInitialContextFactory.register();
+ }
+
+ /**
+ * Starts the builder, which in particular, mocks JNDI, binds the datasource
+ * the JNDI where the persistence unit expects it, creates the entity
+ * manager factory, and forces creation of the database schema.
+ */
+ public void start() throws Exception {
+ try {
+ InitialContext ctx = new InitialContext();
+ ctx.bind(persistenceUnit.getJndiName(), dataSource);
+ } catch (NamingException e) {
+ throw new RuntimeException("JNDI problem", e);
+ }
+ factory = createFactory();
+ execute(new JpaUnitOfWork<Void>() {
+ public Void execute(EntityManager em) {
+ // Empty, just to trigger database schema creation.
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Stops the entity manager factory and disables JNDI mocking.
+ */
+ public void stop() {
+ StubInitialContextFactory.unregister();
+ factory.close();
+ }
+
+ /**
+ * Creates a new entity manager factory. Typically not used by test code.
+ * @return Entity manager factory.
+ */
+ public EntityManagerFactory createFactory() {
+ Map<String, String> jpaProps = new TreeMap<String, String>();
+
+ JpaCustomizerBuilder.getCustomizer().customize(persistenceUnit, jpaProps);
+
+ //jpaProps.put("javax.persistence.provider", HibernatePersistence.class.getName());
+ EntityManagerFactory factory = Persistence.createEntityManagerFactory(persistenceUnit
+ .getUnitName(), jpaProps);
+
+ LOGGER.info("Using " + factory.getClass());
+ return factory;
+ }
+
+ /**
+ * Executes a unit of work. This creates an entitymanager and runs the
+ * {@link JpaUnitOfWork#execute(EntityManager)} within a transaction, passing
+ * it the entity manager. Use of this method saves a lot of typing for applications.
+ *
+ * @param aWork Work to execute.
+ * @return The return value of the execute method of the unit of work.
+ */
+ public <T> T execute(JpaUnitOfWork<T> aWork) throws Exception {
+ EntityManager em = factory.createEntityManager();
+ EntityTransaction transaction = em.getTransaction();
+ transaction.begin();
+ try {
+ T value = aWork.execute(em);
+ transaction.commit();
+ return value;
+ } catch (Exception e) {
+ LOGGER.log(Level.WARNING, "Exception occured", e);
+ transaction.rollback();
+ throw e;
+ } finally {
+ em.close();
+ }
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * JPA customizer is used to customize properties for a given JPA implementation.
+ *
+ * Implementations of JpaCustomizer are found using {@link ServiceLoader}. In case
+ * of testing with a specific JPA provider, the customizer library for that JPA provider
+ * must be on the classpath as well.
+ *
+ * @author Erik Brakkee
+ */
+public interface JpaCustomizer {
+
+ void customize(PersistenceUnitDescription aPersistenceUnit, Map<String,String> aJpaProperties);
+
+ ITableFilterSimple getJpaTables();
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+public class JpaCustomizerBuilder {
+
+ private static final ServiceLoader<JpaCustomizer> CUSTOMIZERS =
+ ServiceLoader.load(JpaCustomizer.class);
+
+
+ public static JpaCustomizer getCustomizer() {
+ List<JpaCustomizer> customizers = new ArrayList<JpaCustomizer>();
+ for (JpaCustomizer customizer: CUSTOMIZERS) {
+ customizers.add(customizer);
+ }
+ return new CompositeJpaCustomizer(customizers);
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+
+/**
+ * This class is the entry point for JPA tests. Test code should construct a JpaTester in the
+ * <code>@Before</code> method and call {@link #start()} on it in that method. Also, test code should
+ * call {@link #stop()} on it in the <code>@After</code> method.
+ *
+ * This class is constructed with a description of the persistence unit to be tested. The principle is that
+ * an existing <code>persistence.xml</code> can be tested without change in unit test code.
+ *
+ * It then takes care of the following:
+ * <ul>
+ * <li> Creating an inmemory database for testing (default) or connecting to an external database.
+ * See {@link DatabaseBuilder} for more information on how a databse is obtained.
+ * </li>
+ * <li> Drop all database tables that are related to the persistence unit under test, including JPA provider
+ * specific tables.
+ * </li>
+ * <li> Creating a datasource for the database and make the datasource available through JNDI.
+ * </li>
+ * <li> Creating the entity manager factory for JPA and configuring it in such a way that schema creation
+ * happens. (Typically, schema creation will be disabled in the persistence.xml but this utility enables it
+ * for unit test).
+ * </li>
+ * <li> Creating a DBUnit database tester which is appropriately configured for the persistence unit under test.
+ * </li>
+ * </ul>
+ *
+ * The main entry point for all this functionality is the {@link PersistenceUnitDescription} which describes the
+ * persistence unit and must be provided at construction of the <code>JpaTester</code>
+ *
+ * NOTE: Persistence XML files should be explicitly configured with the classes that are part of the persistence unit
+ * since scanning of classes does not work correctly in a unit test environment. This is currently the only limitation.
+ */
+public class JpaTester {
+
+ private PersistenceUnitDescription persistenceUnit;
+ private Database db;
+ private DataSource dataSource;
+ private DatabaseUtils dbUtils;
+ private JpaBuilder jpaBuilder;
+ private IDatabaseTester dbTester;
+
+ /**
+ * Constructs the tester.
+ * @param aPersistenceUnit Persistence unit under test.
+ */
+ public JpaTester(PersistenceUnitDescription aPersistenceUnit) {
+ persistenceUnit = aPersistenceUnit;
+ }
+
+ /**
+ * Starts the tester. This must be called prior to running the test.
+ * @throws Exception
+ */
+ public void start() throws Exception {
+ db = DatabaseBuilder.getDatabase();
+ dataSource = db.start();
+
+ dbUtils = new DatabaseUtils(dataSource, persistenceUnit.getTables());
+ dbUtils.dropTables();
+ dbUtils.dropTables(JpaCustomizerBuilder.getCustomizer().getJpaTables());
+
+ jpaBuilder = new JpaBuilder(dataSource, persistenceUnit);
+ jpaBuilder.start();
+
+ // db tester should be created after Jpa builder because jpa builder
+ // creates the
+ // tables that the tester looks at when it is initialized.
+ dbTester = dbUtils.createDbTester();
+ }
+
+ /**
+ * Stops the tester. This must be called after the test.
+ */
+ public void stop() {
+ if (jpaBuilder != null) {
+ jpaBuilder.stop();
+ }
+ if (db != null) {
+ db.stop();
+ }
+ }
+
+ public Database getDb() {
+ return db;
+ }
+
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ public IDatabaseTester getDbTester() {
+ return dbTester;
+ }
+
+ public DatabaseUtils getDbUtils() {
+ return dbUtils;
+ }
+
+ public JpaBuilder getJpaBuilder() {
+ return jpaBuilder;
+ }
+
+ public PersistenceUnitDescription getPersistenceUnit() {
+ return persistenceUnit;
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+public class PersistenceUnitDescription {
+
+ private String jndiName;
+ private String unitName;
+ private ITableFilterSimple tables;
+
+ public PersistenceUnitDescription(String aJndiName, String aUnitName, ITableFilterSimple aTables) {
+ jndiName = aJndiName;
+ unitName = aUnitName;
+ tables = aTables;
+ }
+
+ public String getJndiName() {
+ return jndiName;
+ }
+
+ public String getUnitName() {
+ return unitName;
+ }
+
+ public ITableFilterSimple getTables() {
+ return tables;
+ }
+}
--- /dev/null
+/**
+ * This package provide a number of utilities for database testing and in particular with
+ * JPA.
+ *
+ * The following utilities are available:
+ * <ul>
+ * <li> {@link JpaTester}: The main entry point for all JPA tests.
+ * </li>
+ * <li> {@link JpaBuilder}: A utility constructed by <code>JpaTester</code> that provides a callback based
+ * style of working with transaction-scoped entity managers.
+ * </li>
+ * <li> {@link DatabaseUtils}: A utility constructed by <code>JpaTester</code> for working with databases in general. Test code will not use this
+ * utility often.
+ * </li>
+ * <li> {@link org.dbunit.IDatabaseTester}: A DB unit database tester. The test code can use this database tester.
+ * It is also created by <code>JpaTester</code>
+ * </li>
+ * <li> {@link DatabaseBuilder}: A utility by which test code can transparently create an inmemory database or
+ * connect to an external database. This is also used by <code>JpaTester</code>
+ * </li>
+ * </ul>
+ */
+package org.wamblee.support.persistence;
--- /dev/null
+org.wamblee.support.persistence.DerbyDatabaseProvider
+org.wamblee.support.persistence.ExternalDatabaseProvider
--- /dev/null
+package org.wamblee.support.jndi;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.jndi.StubInitialContextFactory;
+
+public class StubInitiaContextFactoryTest {
+
+ @Before
+ @After
+ public void setUp() {
+ StubInitialContextFactory.unregister();
+ }
+
+
+ @Test(expected = NamingException.class)
+ public void testLookupNotRegistered() throws Exception {
+ InitialContext ctx = new InitialContext();
+ ctx.bind("a/b", "hallo");
+ }
+
+ @Test
+ public void testLookup() throws Exception {
+ StubInitialContextFactory.register();
+
+ InitialContext ctx = new InitialContext();
+ ctx.bind("a/b", "hallo");
+
+ ctx = new InitialContext();
+ Object obj = ctx.lookup("a/b");
+
+ assertEquals("hallo", obj);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseDescription;
+
+public class DatabaseBuilderTest {
+
+
+ @Test
+ public void testListAvailableDatabases() {
+ for (DatabaseDescription description: DatabaseBuilder.getSupportedDatabases()) {
+ System.out.println(description);
+ }
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+public class DatabaseUtilsTestBase {
+ private Database db;
+ private DataSource dataSource;
+ private PersistenceUnitDescription persistenceUnit;
+ private JpaBuilder builder;
+ private DatabaseUtils dbutils;
+ private IDatabaseTester dbtester;
+
+ @Before
+ public void setUp() throws Exception {
+ db = DatabaseBuilder.getDatabase();
+ dataSource = db.start();
+
+ persistenceUnit = new MyPersistenceUnit();
+
+ dbutils = new DatabaseUtils(dataSource, persistenceUnit.getTables());
+ dbutils.dropTables();
+ dbutils.dropTables(JpaCustomizerBuilder.getCustomizer().getJpaTables());
+
+ builder = new JpaBuilder(dataSource, persistenceUnit);
+ builder.start();
+
+ dbtester = dbutils.createDbTester();
+ }
+
+ @After
+ public void tearDown() {
+ builder.stop();
+ db.stop();
+ }
+
+ @Test
+ public void testTablesCorrect() throws Exception {
+ String[] tables = dbutils.getTableNames();
+ assertEquals(1, tables.length);
+ assertEquals("XYZ_MYENTITY", tables[0]);
+ }
+
+ @Test
+ public void testDeleteTables() throws Exception {
+ String[] tables = dbutils.getTableNames();
+ assertEquals(1, tables.length);
+ assertEquals("XYZ_MYENTITY", tables[0]);
+
+ // Put some data in the database.
+ builder.execute(new JpaUnitOfWork<Void>() {
+ public Void execute(EntityManager em) {
+ MyEntity entity = new MyEntity("a", "b");
+ em.persist(entity);
+ return null;
+ }
+ });
+
+ // Verify one row is written (using Db unit)
+ ITable table = dbtester.getDataSet().getTable("XYZ_MYENTITY");
+ assertEquals(1, table.getRowCount());
+
+ // Clean the database
+ dbutils.cleanDatabase();
+ table = dbtester.getDataSet().getTable("XYZ_MYENTITY");
+ assertEquals(0, table.getRowCount());
+
+ // Now drop the database
+ dbutils.dropTables();
+ dbutils.dropTables(JpaCustomizerBuilder.getCustomizer().getJpaTables());
+ tables = dbutils.getTableNames();
+ assertEquals(0, tables.length);
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+
+
+public class DerbyDatabaseTest {
+
+ private Database db;
+ private DataSource ds;
+
+ @Before
+ public void setUp() {
+ db = DatabaseBuilder.getDatabase();
+ ds = db.start();
+ }
+
+ @After
+ public void tearDown() {
+ db.stop();
+ }
+
+ @Test
+ public void testConnect() throws Exception {
+ Connection conn = ds.getConnection();
+ try {
+ System.out.println("Database name: " + conn.getMetaData().getDatabaseProductName());
+ } finally {
+ conn.close();
+ }
+ }
+
+ @Test
+ public void testUseASecondTimeInTheSameTestCase() throws Exception {
+ testConnect();
+ tearDown();
+ setUp();
+ testConnect();
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import java.sql.Connection;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseProvider;
+import org.wamblee.support.persistence.ExternalDatabase;
+
+import static junit.framework.TestCase.*;
+
+public class ExternalDatabaseTest {
+
+ @Test
+ public void testExternalDB() throws Exception {
+ // Connect to inmemory db using External database class.
+
+ Database inmemory = DatabaseBuilder
+ .getDatabase(DatabaseProvider.CAPABILITY_IN_MEMORY);
+ try {
+ inmemory.start();
+
+ System.setProperty(ExternalDatabase.DB_URL_PROP, inmemory
+ .getExternalJdbcUrl());
+ System.setProperty(ExternalDatabase.DB_USER_PROP, inmemory
+ .getUsername());
+ System.setProperty(ExternalDatabase.DB_PASSWORD_PROP, inmemory
+ .getPassword());
+
+ Database external = DatabaseBuilder
+ .getDatabase(DatabaseProvider.CAPABILITY_EXTERNAL);
+ assertTrue(external instanceof ExternalDatabase);
+ try {
+ DataSource ds = external.start();
+ Connection conn = ds.getConnection();
+ try {
+ System.out.println("Database name: "
+ + conn.getMetaData().getDatabaseProductName());
+ } finally {
+ conn.close();
+ }
+ } finally {
+ external.stop();
+ }
+ } finally {
+ inmemory.stop();
+ }
+
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "XYZ_MYENTITY")
+public class MyEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ private String sleuteltje;
+ private String value;
+
+
+ public MyEntity() {
+ // Empty
+ }
+
+ public MyEntity(String aKey, String aValue) {
+ sleuteltje = aKey;
+ value = aValue;
+ }
+
+ public String getKey() {
+ return sleuteltje;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseTestCase;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.JpaTester;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+import static junit.framework.Assert.*;
+
+
+/**
+ * This class shows an example of how to test an entity using jpa.
+ */
+public class MyEntityExampleTestBase {
+
+ // This is the magical object that does all the test setup.
+ private JpaTester jpaTester;
+
+ // The jpa tester initializes a lot for us....
+
+ // A JPA builder that provides a transaction scoped entity manager for us.
+ private JpaBuilder builder;
+
+ // The database tester for dbunit which is appropriately configured for our persistence unit.
+ private IDatabaseTester dbtester;
+
+ // Database utilities with some additional functionality for working with the databse
+ // such as dropping tables, cleaning tables, etc.
+ private DatabaseUtils dbutils;
+
+ @Before
+ public void setUp() throws Exception {
+
+ // First we create the JpaTester by telling us which persistence unit we are going to test
+ jpaTester = new JpaTester(new MyPersistenceUnit());
+ jpaTester.start();
+
+ // Retrieve some useful objects fromt he jpa tester. It also provides direct access to the datasource
+ // but we don't need it. We can use datbase utils if we want to execute straight JDBC calls.
+ builder = jpaTester.getJpaBuilder();
+ dbtester = jpaTester.getDbTester();
+ dbutils = jpaTester.getDbUtils();
+ }
+
+ @After
+ public void tearDown() {
+ jpaTester.stop();
+ }
+
+ @Test
+ public void testEntityPersistence() throws Exception {
+
+ // Use the JPA builder to create a transaction scoped entity manager for as and execute the
+ // unit of work.
+ builder.execute(new JpaUnitOfWork<Void>() {
+ public Void execute(EntityManager em) {
+ MyEntity entity = new MyEntity("a", "b");
+ em.persist(entity);
+ return null;
+ }
+ });
+
+ // Verify one row is written (using Db unit)
+ ITable table = dbtester.getDataSet().getTable("XYZ_MYENTITY");
+ assertEquals(1, table.getRowCount());
+
+ assertEquals("a", table.getValue(0, "SLEUTELTJE"));
+ assertEquals("b", table.getValue(0, "VALUE"));
+
+ // For this simple test, it can also be done through DatabaseUtils
+ assertEquals(1, dbutils.getTableSize("XYZ_MYENTITY"));
+
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+
+/**
+ * This class describes the persistence unit that we are testing.
+ */
+public class MyPersistenceUnit extends PersistenceUnitDescription {
+ private static final String JNDI_NAME = "wamblee/support/test";
+ private static final String PERSISTENCE_UNIT_NAME = "org.wamblee.jee.support-test";
+
+ public MyPersistenceUnit() {
+ super(JNDI_NAME, PERSISTENCE_UNIT_NAME, new MyTables());
+ }
+}
--- /dev/null
+package org.wamblee.support.persistence;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * This class describes the tables we are testing.
+ * This implementation selects all tables with the "XYZ" prefix.
+ */
+public class MyTables implements ITableFilterSimple {
+
+ /**
+ *
+ * @param aJpaTables Specific tables used by the JPA provider in addition to those for the
+ * entities.
+ */
+ public MyTables() {
+ // Empty.
+ }
+
+ public boolean accept(String tableName) throws DataSetException {
+ return tableName.startsWith("XYZ_");
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence version="1.0"
+ xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
+ <persistence-unit name="org.wamblee.jee.support-test"
+ transaction-type="JTA">
+ <jta-data-source>wamblee/support/test</jta-data-source>
+ <class>org.wamblee.support.persistence.MyEntity</class>
+ <properties>
+ <!--
+ <property name="toplink.ddl-generation"
+ value="drop-and-create-tables" />
+ -->
+ <!-- <property name="toplink.ddl-generation" value="none" /> -->
+ <property name="toplink.ddl-generation" value="create-tables" />
+
+ <!--
+ <property name="toplink.create-ddl-jdbc-file-name"
+ value="create_tm_ddl.jdbc" />
+ -->
+ <!--
+ <property name="toplink.drop-ddl-jdbc-file-name"
+ value="drop_tm_ddl.jdbc" />
+ -->
+
+ <property name="toplink.logging.level" value="INFO" />
+
+ <!--
+ Toplink 2nd level cache disable for JSROuteRule, this is required so
+ that changes made by one instance in a cluster become visible to the
+ cluster. See
+ http://www.oracle.com/technology/products/ias/toplink/JPA/essentials/toplink-jpa-extensions.html#TopLinkCaching
+ for information on these toplink caching properties.
+ -->
+ <property name="toplink.cache.shared.default" value="false" />
+
+ </properties>
+
+ </persistence-unit>
+</persistence>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-hibernate</artifactId>
+ <packaging>jar</packaging>
+ <name>/test/hibernate</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ <type>test-jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-hibernate-jpa</artifactId>
+ <version>0.2</version>
+ </dependency>
+
+ </dependencies>
+
+</project>
--- /dev/null
+package org.wamblee.support.persistence.hibernate;
+
+import java.util.Map;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.wamblee.support.persistence.JpaCustomizer;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+
+public class HibernateJpaCustomizer implements JpaCustomizer {
+
+ public HibernateJpaCustomizer() {
+ // Empty
+ }
+
+ @Override
+ public void customize(PersistenceUnitDescription aPersistenceUnit, Map<String, String> aJpaProperties) {
+ // Hibernate: Override transaction type and datasource
+ aJpaProperties.put("javax.persistence.transactionType", "RESOURCE_LOCAL");
+ aJpaProperties.put("javax.persistence.jtaDataSource", null);
+ aJpaProperties.put("javax.persistence.nonJtaDataSource", aPersistenceUnit.getJndiName());
+
+ // Hibernate schema generation
+ aJpaProperties.put("hibernate.hbm2ddl.auto", "create");
+ }
+
+ @Override
+ public ITableFilterSimple getJpaTables() {
+ return new HibernateTables();
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence.hibernate;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * Toplink-specific tables.
+ */
+public class HibernateTables implements ITableFilterSimple {
+
+ private static final List<String> TABLES = Arrays.asList(new String[] { "" } );
+
+ public boolean accept(String aTableName) throws DataSetException {
+ return TABLES.contains(aTableName);
+ }
+
+}
--- /dev/null
+org.wamblee.support.persistence.hibernate.HibernateJpaCustomizer
--- /dev/null
+package org.wamblee.support.persistence.hibernate;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.DatabaseUtilsTestBase;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+public class DatabaseUtilsTest extends DatabaseUtilsTestBase {
+ // Empty, all tests inherited
+}
--- /dev/null
+package org.wamblee.support.persistence.hibernate;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseTestCase;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.JpaTester;
+import org.wamblee.support.persistence.MyEntityExampleTestBase;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+import static junit.framework.Assert.*;
+
+
+/**
+ * This class shows an example of how to test an entity using jpa.
+ */
+public class MyEntityExampleTest extends MyEntityExampleTestBase {
+ // Empty, all tests are inherited
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test</artifactId>
+ <packaging>pom</packaging>
+ <name>/test</name>
+ <url>http://wamblee.org</url>
+
+ <modules>
+ <module>enterprise</module>
+ <module>hibernate</module>
+ </modules>
+
+ <profiles>
+ <profile>
+ <id>all</id>
+ <activation>
+ <property>
+ <name>!performRelease</name>
+ </property>
+ </activation>
+ <modules>
+ <module>eclipselink</module>
+ <module>toplink-essentials</module>
+ </modules>
+ </profile>
+ </profiles>
+
+</project>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-utils</artifactId>
+ <version>0.2</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-toplink-essentials</artifactId>
+ <packaging>jar</packaging>
+ <name>/test/toplinkessentials</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-test-enterprise</artifactId>
+ <version>0.2</version>
+ <type>test-jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.dbunit</groupId>
+ <artifactId>dbunit</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>toplink.essentials</groupId>
+ <artifactId>toplink-essentials</artifactId>
+ </dependency>
+
+
+ </dependencies>
+
+ <repositories>
+ <repository>
+ <id>javaee</id>
+ <name>Java EE repo at SUN</name>
+ <url>http://download.java.net/maven/1</url>
+ <layout>legacy</layout>
+ </repository>
+
+ </repositories>
+
+</project>
--- /dev/null
+package org.wamblee.support.persistence.toplink;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import oracle.toplink.essentials.jndi.JNDIConnector;
+import oracle.toplink.essentials.sessions.DatabaseLogin;
+import oracle.toplink.essentials.sessions.Session;
+import oracle.toplink.essentials.threetier.ServerSession;
+import oracle.toplink.essentials.tools.sessionconfiguration.SessionCustomizer;
+
+/**
+ * See http://wiki.eclipse.org/Customizing_the_EclipseLink_Application_(ELUG) Use for clients that would like to use a
+ * JTA SE pu instead of a RESOURCE_LOCAL SE pu.
+ *
+ * This utility also makes sure that using a persistence.xml with a JTA datasource works in a standalone Java SE
+ * environment together with our JNDI stub.
+ */
+public class JndiSessionCustomizer
+ implements SessionCustomizer {
+
+ public JndiSessionCustomizer() {
+ // Empty.
+ }
+
+ /**
+ * Get a dataSource connection and set it on the session with lookupType=STRING_LOOKUP
+ */
+ public void customize(Session session) throws Exception {
+ JNDIConnector connector = null;
+ Context context = null;
+ try {
+ context = new InitialContext();
+ if(null != context) {
+ connector = (JNDIConnector)session.getLogin().getConnector(); // possible CCE
+ // Change from COMPOSITE_NAME_LOOKUP to STRING_LOOKUP
+ // Note: if both jta and non-jta elements exist this will only change the first one - and may still result in
+ // the COMPOSITE_NAME_LOOKUP being set
+ // Make sure only jta-data-source is in persistence.xml with no non-jta-data-source property set
+ connector.setLookupType(JNDIConnector.STRING_LOOKUP);
+
+ // Or, if you are specifying both JTA and non-JTA in your persistence.xml then set both connectors to be safe
+ JNDIConnector writeConnector = (JNDIConnector)session.getLogin().getConnector();
+ writeConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
+ JNDIConnector readConnector =
+ (JNDIConnector)((DatabaseLogin)((ServerSession)session).getReadConnectionPool().getLogin()).getConnector();
+ readConnector.setLookupType(JNDIConnector.STRING_LOOKUP);
+
+ System.out.println("JndiSessionCustomizer: configured " + connector.getName());
+ }
+ else {
+ throw new Exception("JndiSessionCustomizer: Context is null");
+ }
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.wamblee.support.persistence.toplink;
+
+import java.util.Map;
+
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.wamblee.support.persistence.JpaCustomizer;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+
+public class ToplinkJpaCustomizer implements JpaCustomizer {
+
+ public ToplinkJpaCustomizer() {
+ // Empty
+ }
+
+ @Override
+ public void customize(PersistenceUnitDescription aPersistenceUnit, Map<String, String> aJpaProperties) {
+ // Hack to make JNDI lookup of the datasource work with toplink
+ aJpaProperties.put("toplink.session.customizer", JndiSessionCustomizer.class
+ .getName());
+
+ // DDL generation for toplink
+ aJpaProperties.put("toplink.ddl-generation", "create-tables");
+ }
+
+ @Override
+ public ITableFilterSimple getJpaTables() {
+ return new ToplinkTables();
+ }
+
+}
--- /dev/null
+package org.wamblee.support.persistence.toplink;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.dbunit.dataset.DataSetException;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+
+/**
+ * Toplink-specific tables.
+ */
+public class ToplinkTables implements ITableFilterSimple {
+
+ private static final List<String> TABLES = Arrays.asList(new String[] { "SEQUENCE" } );
+
+ public boolean accept(String aTableName) throws DataSetException {
+ return TABLES.contains(aTableName);
+ }
+
+}
--- /dev/null
+org.wamblee.support.persistence.toplink.ToplinkJpaCustomizer
--- /dev/null
+package org.wamblee.support.persistence.toplink;
+
+import static junit.framework.Assert.assertEquals;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.Database;
+import org.wamblee.support.persistence.DatabaseBuilder;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.DatabaseUtilsTestBase;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.PersistenceUnitDescription;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+public class DatabaseUtilsTest extends DatabaseUtilsTestBase {
+ // Empty, all tests inherited
+}
--- /dev/null
+package org.wamblee.support.persistence.toplink;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Persistence;
+import javax.sql.DataSource;
+
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseTestCase;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.dataset.ITable;
+import org.dbunit.dataset.filter.ITableFilterSimple;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.wamblee.support.persistence.DatabaseUtils;
+import org.wamblee.support.persistence.JpaBuilder;
+import org.wamblee.support.persistence.JpaTester;
+import org.wamblee.support.persistence.MyEntityExampleTestBase;
+import org.wamblee.support.persistence.JpaBuilder.JpaUnitOfWork;
+
+import static junit.framework.Assert.*;
+
+
+/**
+ * This class shows an example of how to test an entity using jpa.
+ */
+public class MyEntityExampleTest extends MyEntityExampleTestBase {
+ // Empty, all tests are inherited
+}